From 71654810756f3be977359e5fdcd79375aab47c52 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 22 Sep 2022 15:12:05 +0100 Subject: [PATCH 01/55] Updated auth controllers with property types --- app/Http/Controllers/Auth/ConfirmEmailController.php | 6 +++--- app/Http/Controllers/Auth/LoginController.php | 4 ++-- app/Http/Controllers/Auth/RegisterController.php | 6 ++---- app/Http/Controllers/Auth/SocialController.php | 6 +++--- app/Http/Controllers/Auth/UserInviteController.php | 4 ++-- 5 files changed, 12 insertions(+), 14 deletions(-) diff --git a/app/Http/Controllers/Auth/ConfirmEmailController.php b/app/Http/Controllers/Auth/ConfirmEmailController.php index 873d88475..ea633ff3a 100644 --- a/app/Http/Controllers/Auth/ConfirmEmailController.php +++ b/app/Http/Controllers/Auth/ConfirmEmailController.php @@ -14,9 +14,9 @@ use Illuminate\Http\Request; class ConfirmEmailController extends Controller { - protected $emailConfirmationService; - protected $loginService; - protected $userRepo; + protected EmailConfirmationService $emailConfirmationService; + protected LoginService $loginService; + protected UserRepo $userRepo; /** * Create a new controller instance. diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 1d6a36c5b..bf40b0e03 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -31,8 +31,8 @@ class LoginController extends Controller /** * Redirection paths. */ - protected $redirectTo = '/'; - protected $redirectPath = '/'; + protected string $redirectTo = '/'; + protected string $redirectPath = '/'; protected SocialAuthService $socialAuthService; protected LoginService $loginService; diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php index 15ee78d50..d74944800 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -35,11 +35,9 @@ class RegisterController extends Controller /** * Where to redirect users after login / registration. - * - * @var string */ - protected $redirectTo = '/'; - protected $redirectPath = '/'; + protected string $redirectTo = '/'; + protected string $redirectPath = '/'; /** * Create a new controller instance. diff --git a/app/Http/Controllers/Auth/SocialController.php b/app/Http/Controllers/Auth/SocialController.php index 1691668a2..cf7ae9041 100644 --- a/app/Http/Controllers/Auth/SocialController.php +++ b/app/Http/Controllers/Auth/SocialController.php @@ -16,9 +16,9 @@ use Laravel\Socialite\Contracts\User as SocialUser; class SocialController extends Controller { - protected $socialAuthService; - protected $registrationService; - protected $loginService; + protected SocialAuthService $socialAuthService; + protected RegistrationService $registrationService; + protected LoginService $loginService; /** * SocialController constructor. diff --git a/app/Http/Controllers/Auth/UserInviteController.php b/app/Http/Controllers/Auth/UserInviteController.php index 27b20f831..213959abd 100644 --- a/app/Http/Controllers/Auth/UserInviteController.php +++ b/app/Http/Controllers/Auth/UserInviteController.php @@ -15,8 +15,8 @@ use Illuminate\Validation\Rules\Password; class UserInviteController extends Controller { - protected $inviteService; - protected $userRepo; + protected UserInviteService $inviteService; + protected UserRepo $userRepo; /** * Create a new controller instance. From f4388d5e4a6353f6f79e03c638ed2971fbf48c54 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 22 Sep 2022 16:54:27 +0100 Subject: [PATCH 02/55] Removed usage of laravel/ui dependency Brings app auth controller handling aligned within the app, rather than having many overrides of the framwork packages causing confusion and messiness over time. --- app/Auth/Access/LoginService.php | 2 + app/Auth/UserRepo.php | 5 +- .../Auth/ForgotPasswordController.php | 23 +-- app/Http/Controllers/Auth/LoginController.php | 167 ++++++------------ .../Controllers/Auth/RegisterController.php | 57 +----- .../Auth/ResetPasswordController.php | 94 ++++++---- app/Http/Controllers/Auth/Saml2Controller.php | 2 +- .../Controllers/Auth/SocialController.php | 2 +- app/Http/Controllers/Auth/ThrottlesLogins.php | 92 ++++++++++ .../Controllers/Auth/UserInviteController.php | 3 +- composer.json | 1 - composer.lock | 63 +------ 12 files changed, 232 insertions(+), 279 deletions(-) create mode 100644 app/Http/Controllers/Auth/ThrottlesLogins.php diff --git a/app/Auth/Access/LoginService.php b/app/Auth/Access/LoginService.php index f41570417..c80943166 100644 --- a/app/Auth/Access/LoginService.php +++ b/app/Auth/Access/LoginService.php @@ -5,6 +5,7 @@ namespace BookStack\Auth\Access; use BookStack\Actions\ActivityType; use BookStack\Auth\Access\Mfa\MfaSession; use BookStack\Auth\User; +use BookStack\Exceptions\LoginAttemptException; use BookStack\Exceptions\StoppedAuthenticationException; use BookStack\Facades\Activity; use BookStack\Facades\Theme; @@ -149,6 +150,7 @@ class LoginService * May interrupt the flow if extra authentication requirements are imposed. * * @throws StoppedAuthenticationException + * @throws LoginAttemptException */ public function attempt(array $credentials, string $method, bool $remember = false): bool { diff --git a/app/Auth/UserRepo.php b/app/Auth/UserRepo.php index 28ce96c49..c589fd964 100644 --- a/app/Auth/UserRepo.php +++ b/app/Auth/UserRepo.php @@ -10,6 +10,7 @@ use BookStack\Exceptions\UserUpdateException; use BookStack\Facades\Activity; use BookStack\Uploads\UserAvatars; use Exception; +use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Log; use Illuminate\Support\Str; @@ -61,7 +62,7 @@ class UserRepo $user = new User(); $user->name = $data['name']; $user->email = $data['email']; - $user->password = bcrypt(empty($data['password']) ? Str::random(32) : $data['password']); + $user->password = Hash::make(empty($data['password']) ? Str::random(32) : $data['password']); $user->email_confirmed = $emailConfirmed; $user->external_auth_id = $data['external_auth_id'] ?? ''; @@ -126,7 +127,7 @@ class UserRepo } if (!empty($data['password'])) { - $user->password = bcrypt($data['password']); + $user->password = Hash::make($data['password']); } if (!empty($data['language'])) { diff --git a/app/Http/Controllers/Auth/ForgotPasswordController.php b/app/Http/Controllers/Auth/ForgotPasswordController.php index b345fad1c..2bdc31df5 100644 --- a/app/Http/Controllers/Auth/ForgotPasswordController.php +++ b/app/Http/Controllers/Auth/ForgotPasswordController.php @@ -4,24 +4,11 @@ namespace BookStack\Http\Controllers\Auth; use BookStack\Actions\ActivityType; use BookStack\Http\Controllers\Controller; -use Illuminate\Foundation\Auth\SendsPasswordResetEmails; use Illuminate\Http\Request; use Illuminate\Support\Facades\Password; class ForgotPasswordController extends Controller { - /* - |-------------------------------------------------------------------------- - | Password Reset Controller - |-------------------------------------------------------------------------- - | - | This controller is responsible for handling password reset emails and - | includes a trait which assists in sending these notifications from - | your application to your users. Feel free to explore this trait. - | - */ - use SendsPasswordResetEmails; - /** * Create a new controller instance. * @@ -33,6 +20,14 @@ class ForgotPasswordController extends Controller $this->middleware('guard:standard'); } + /** + * Display the form to request a password reset link. + */ + public function showLinkRequestForm() + { + return view('auth.passwords.email'); + } + /** * Send a reset link to the given user. * @@ -49,7 +44,7 @@ class ForgotPasswordController extends Controller // We will send the password reset link to this user. Once we have attempted // to send the link, we will examine the response then see the message we // need to show to the user. Finally, we'll send out a proper response. - $response = $this->broker()->sendResetLink( + $response = Password::broker()->sendResetLink( $request->only('email') ); diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index bf40b0e03..e16feb079 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -8,31 +8,14 @@ use BookStack\Exceptions\LoginAttemptEmailNeededException; use BookStack\Exceptions\LoginAttemptException; use BookStack\Facades\Activity; use BookStack\Http\Controllers\Controller; -use Illuminate\Foundation\Auth\AuthenticatesUsers; +use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Auth; use Illuminate\Validation\ValidationException; class LoginController extends Controller { - /* - |-------------------------------------------------------------------------- - | Login Controller - |-------------------------------------------------------------------------- - | - | This controller handles authenticating users for the application and - | redirecting them to your home screen. The controller uses a trait - | to conveniently provide its functionality to your applications. - | - */ - use AuthenticatesUsers { - logout as traitLogout; - } - - /** - * Redirection paths. - */ - protected string $redirectTo = '/'; - protected string $redirectPath = '/'; + use ThrottlesLogins; protected SocialAuthService $socialAuthService; protected LoginService $loginService; @@ -48,21 +31,6 @@ class LoginController extends Controller $this->socialAuthService = $socialAuthService; $this->loginService = $loginService; - - $this->redirectPath = url('/'); - } - - public function username() - { - return config('auth.method') === 'standard' ? 'email' : 'username'; - } - - /** - * Get the needed authorization credentials from the request. - */ - protected function credentials(Request $request) - { - return $request->only('username', 'email', 'password'); } /** @@ -98,29 +66,15 @@ class LoginController extends Controller /** * Handle a login request to the application. - * - * @param \Illuminate\Http\Request $request - * - * @throws \Illuminate\Validation\ValidationException - * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|\Illuminate\Http\JsonResponse */ public function login(Request $request) { $this->validateLogin($request); $username = $request->get($this->username()); - // If the class is using the ThrottlesLogins trait, we can automatically throttle - // the login attempts for this application. We'll key this by the username and - // the IP address of the client making these requests into this application. - if ( - method_exists($this, 'hasTooManyLoginAttempts') && - $this->hasTooManyLoginAttempts($request) - ) { - $this->fireLockoutEvent($request); - + // Check login throttling attempts to see if they've gone over the limit + if ($this->hasTooManyLoginAttempts($request)) { Activity::logFailedLogin($username); - return $this->sendLockoutResponse($request); } @@ -134,24 +88,62 @@ class LoginController extends Controller return $this->sendLoginAttemptExceptionResponse($exception, $request); } - // If the login attempt was unsuccessful we will increment the number of attempts - // to login and redirect the user back to the login form. Of course, when this - // user surpasses their maximum number of attempts they will get locked out. + // On unsuccessful login attempt, Increment login attempts for throttling and log failed login. $this->incrementLoginAttempts($request); - Activity::logFailedLogin($username); - return $this->sendFailedLoginResponse($request); + // Throw validation failure for failed login + throw ValidationException::withMessages([ + $this->username() => [trans('auth.failed')], + ])->redirectTo('/login'); + } + + /** + * Logout user and perform subsequent redirect. + */ + public function logout(Request $request) + { + Auth::guard()->logout(); + $request->session()->invalidate(); + $request->session()->regenerateToken(); + + $redirectUri = $this->shouldAutoInitiate() ? '/login?prevent_auto_init=true' : '/'; + + return redirect($redirectUri); + } + + /** + * Get the expected username input based upon the current auth method. + */ + protected function username(): string + { + return config('auth.method') === 'standard' ? 'email' : 'username'; + } + + /** + * Get the needed authorization credentials from the request. + */ + protected function credentials(Request $request): array + { + return $request->only('username', 'email', 'password'); + } + + /** + * Send the response after the user was authenticated. + * @return RedirectResponse + */ + protected function sendLoginResponse(Request $request) + { + $request->session()->regenerate(); + $this->clearLoginAttempts($request); + + return redirect()->intended('/'); } /** * Attempt to log the user into the application. - * - * @param \Illuminate\Http\Request $request - * - * @return bool */ - protected function attemptLogin(Request $request) + protected function attemptLogin(Request $request): bool { return $this->loginService->attempt( $this->credentials($request), @@ -160,29 +152,12 @@ class LoginController extends Controller ); } - /** - * The user has been authenticated. - * - * @param \Illuminate\Http\Request $request - * @param mixed $user - * - * @return mixed - */ - protected function authenticated(Request $request, $user) - { - return redirect()->intended($this->redirectPath()); - } /** * Validate the user login request. - * - * @param \Illuminate\Http\Request $request - * - * @throws \Illuminate\Validation\ValidationException - * - * @return void + * @throws ValidationException */ - protected function validateLogin(Request $request) + protected function validateLogin(Request $request): void { $rules = ['password' => ['required', 'string']]; $authMethod = config('auth.method'); @@ -216,22 +191,6 @@ class LoginController extends Controller return redirect('/login'); } - /** - * Get the failed login response instance. - * - * @param \Illuminate\Http\Request $request - * - * @throws \Illuminate\Validation\ValidationException - * - * @return \Symfony\Component\HttpFoundation\Response - */ - protected function sendFailedLoginResponse(Request $request) - { - throw ValidationException::withMessages([ - $this->username() => [trans('auth.failed')], - ])->redirectTo('/login'); - } - /** * Update the intended URL location from their previous URL. * Ignores if not from the current app instance or if from certain @@ -271,20 +230,4 @@ class LoginController extends Controller return $autoRedirect && count($socialDrivers) === 0 && in_array($authMethod, ['oidc', 'saml2']); } - - /** - * Logout user and perform subsequent redirect. - * - * @param \Illuminate\Http\Request $request - * - * @return mixed - */ - public function logout(Request $request) - { - $this->traitLogout($request); - - $redirectUri = $this->shouldAutoInitiate() ? '/login?prevent_auto_init=true' : '/'; - - return redirect($redirectUri); - } } diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php index d74944800..262ca540e 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -5,40 +5,20 @@ namespace BookStack\Http\Controllers\Auth; use BookStack\Auth\Access\LoginService; use BookStack\Auth\Access\RegistrationService; use BookStack\Auth\Access\SocialAuthService; -use BookStack\Auth\User; use BookStack\Exceptions\StoppedAuthenticationException; use BookStack\Exceptions\UserRegistrationException; use BookStack\Http\Controllers\Controller; -use Illuminate\Foundation\Auth\RegistersUsers; +use Illuminate\Contracts\Validation\Validator as ValidatorContract; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rules\Password; class RegisterController extends Controller { - /* - |-------------------------------------------------------------------------- - | Register Controller - |-------------------------------------------------------------------------- - | - | This controller handles the registration of new users as well as their - | validation and creation. By default this controller uses a trait to - | provide this functionality without requiring any additional code. - | - */ - use RegistersUsers; - protected SocialAuthService $socialAuthService; protected RegistrationService $registrationService; protected LoginService $loginService; - /** - * Where to redirect users after login / registration. - */ - protected string $redirectTo = '/'; - protected string $redirectPath = '/'; - /** * Create a new controller instance. */ @@ -53,23 +33,6 @@ class RegisterController extends Controller $this->socialAuthService = $socialAuthService; $this->registrationService = $registrationService; $this->loginService = $loginService; - - $this->redirectTo = url('/'); - $this->redirectPath = url('/'); - } - - /** - * Get a validator for an incoming registration request. - * - * @return \Illuminate\Contracts\Validation\Validator - */ - protected function validator(array $data) - { - return Validator::make($data, [ - 'name' => ['required', 'min:2', 'max:100'], - 'email' => ['required', 'email', 'max:255', 'unique:users'], - 'password' => ['required', Password::default()], - ]); } /** @@ -112,22 +75,18 @@ class RegisterController extends Controller $this->showSuccessNotification(trans('auth.register_success')); - return redirect($this->redirectPath()); + return redirect('/'); } /** - * Create a new user instance after a valid registration. - * - * @param array $data - * - * @return User + * Get a validator for an incoming registration request. */ - protected function create(array $data) + protected function validator(array $data): ValidatorContract { - return User::create([ - 'name' => $data['name'], - 'email' => $data['email'], - 'password' => Hash::make($data['password']), + return Validator::make($data, [ + 'name' => ['required', 'min:2', 'max:100'], + 'email' => ['required', 'email', 'max:255', 'unique:users'], + 'password' => ['required', Password::default()], ]); } } diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php index 9df010736..a9914928e 100644 --- a/app/Http/Controllers/Auth/ResetPasswordController.php +++ b/app/Http/Controllers/Auth/ResetPasswordController.php @@ -3,65 +3,87 @@ namespace BookStack\Http\Controllers\Auth; use BookStack\Actions\ActivityType; +use BookStack\Auth\Access\LoginService; +use BookStack\Auth\User; use BookStack\Http\Controllers\Controller; -use Illuminate\Foundation\Auth\ResetsPasswords; +use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Password; +use Illuminate\Support\Str; +use Illuminate\Validation\Rules\Password as PasswordRule; class ResetPasswordController extends Controller { - /* - |-------------------------------------------------------------------------- - | Password Reset Controller - |-------------------------------------------------------------------------- - | - | This controller is responsible for handling password reset requests - | and uses a simple trait to include this behavior. You're free to - | explore this trait and override any methods you wish to tweak. - | - */ - use ResetsPasswords; + protected LoginService $loginService; - protected $redirectTo = '/'; - - /** - * Create a new controller instance. - * - * @return void - */ - public function __construct() + public function __construct(LoginService $loginService) { $this->middleware('guest'); $this->middleware('guard:standard'); + + $this->loginService = $loginService; + } + + /** + * Display the password reset view for the given token. + * If no token is present, display the link request form. + */ + public function showResetForm(Request $request) + { + $token = $request->route()->parameter('token'); + + return view('auth.passwords.reset')->with( + ['token' => $token, 'email' => $request->email] + ); + } + + /** + * Reset the given user's password. + */ + public function reset(Request $request) + { + $request->validate([ + 'token' => 'required', + 'email' => 'required|email', + 'password' => ['required', 'confirmed', PasswordRule::defaults()], + ]); + + // Here we will attempt to reset the user's password. If it is successful we + // will update the password on an actual user model and persist it to the + // database. Otherwise we will parse the error and return the response. + $credentials = $request->only('email', 'password', 'password_confirmation', 'token'); + $response = Password::broker()->reset($credentials, function (User $user, string $password) { + $user->password = Hash::make($password); + $user->setRememberToken(Str::random(60)); + $user->save(); + + $this->loginService->login($user, auth()->getDefaultDriver()); + }); + + // If the password was successfully reset, we will redirect the user back to + // the application's home authenticated view. If there is an error we can + // redirect them back to where they came from with their error message. + return $response === Password::PASSWORD_RESET + ? $this->sendResetResponse() + : $this->sendResetFailedResponse($request, $response); } /** * Get the response for a successful password reset. - * - * @param Request $request - * @param string $response - * - * @return \Illuminate\Http\Response */ - protected function sendResetResponse(Request $request, $response) + protected function sendResetResponse(): RedirectResponse { - $message = trans('auth.reset_password_success'); - $this->showSuccessNotification($message); + $this->showSuccessNotification(trans('auth.reset_password_success')); $this->logActivity(ActivityType::AUTH_PASSWORD_RESET_UPDATE, user()); - return redirect($this->redirectPath()) - ->with('status', trans($response)); + return redirect('/'); } /** * Get the response for a failed password reset. - * - * @param \Illuminate\Http\Request $request - * @param string $response - * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse */ - protected function sendResetFailedResponse(Request $request, $response) + protected function sendResetFailedResponse(Request $request, string $response): RedirectResponse { // We show invalid users as invalid tokens as to not leak what // users may exist in the system. diff --git a/app/Http/Controllers/Auth/Saml2Controller.php b/app/Http/Controllers/Auth/Saml2Controller.php index b84483961..b3f8e7601 100644 --- a/app/Http/Controllers/Auth/Saml2Controller.php +++ b/app/Http/Controllers/Auth/Saml2Controller.php @@ -9,7 +9,7 @@ use Illuminate\Support\Str; class Saml2Controller extends Controller { - protected $samlService; + protected Saml2Service $samlService; /** * Saml2Controller constructor. diff --git a/app/Http/Controllers/Auth/SocialController.php b/app/Http/Controllers/Auth/SocialController.php index cf7ae9041..9ba4028ec 100644 --- a/app/Http/Controllers/Auth/SocialController.php +++ b/app/Http/Controllers/Auth/SocialController.php @@ -28,7 +28,7 @@ class SocialController extends Controller RegistrationService $registrationService, LoginService $loginService ) { - $this->middleware('guest')->only(['getRegister', 'postRegister']); + $this->middleware('guest')->only(['register']); $this->socialAuthService = $socialAuthService; $this->registrationService = $registrationService; $this->loginService = $loginService; diff --git a/app/Http/Controllers/Auth/ThrottlesLogins.php b/app/Http/Controllers/Auth/ThrottlesLogins.php new file mode 100644 index 000000000..7578ba898 --- /dev/null +++ b/app/Http/Controllers/Auth/ThrottlesLogins.php @@ -0,0 +1,92 @@ +limiter()->tooManyAttempts( + $this->throttleKey($request), + $this->maxAttempts() + ); + } + + /** + * Increment the login attempts for the user. + */ + protected function incrementLoginAttempts(Request $request): void + { + $this->limiter()->hit( + $this->throttleKey($request), + $this->decayMinutes() * 60 + ); + } + + /** + * Redirect the user after determining they are locked out. + * @throws ValidationException + */ + protected function sendLockoutResponse(Request $request): \Symfony\Component\HttpFoundation\Response + { + $seconds = $this->limiter()->availableIn( + $this->throttleKey($request) + ); + + throw ValidationException::withMessages([ + $this->username() => [trans('auth.throttle', [ + 'seconds' => $seconds, + 'minutes' => ceil($seconds / 60), + ])], + ])->status(Response::HTTP_TOO_MANY_REQUESTS); + } + + /** + * Clear the login locks for the given user credentials. + */ + protected function clearLoginAttempts(Request $request): void + { + $this->limiter()->clear($this->throttleKey($request)); + } + + /** + * Get the throttle key for the given request. + */ + protected function throttleKey(Request $request): string + { + return Str::transliterate(Str::lower($request->input($this->username())) . '|' . $request->ip()); + } + + /** + * Get the rate limiter instance. + */ + protected function limiter(): RateLimiter + { + return app(RateLimiter::class); + } + + /** + * Get the maximum number of attempts to allow. + */ + public function maxAttempts(): int + { + return 5; + } + + /** + * Get the number of minutes to throttle for. + */ + public function decayMinutes(): int + { + return 1; + } +} diff --git a/app/Http/Controllers/Auth/UserInviteController.php b/app/Http/Controllers/Auth/UserInviteController.php index 213959abd..5b3bba6ff 100644 --- a/app/Http/Controllers/Auth/UserInviteController.php +++ b/app/Http/Controllers/Auth/UserInviteController.php @@ -11,6 +11,7 @@ use Exception; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Routing\Redirector; +use Illuminate\Support\Facades\Hash; use Illuminate\Validation\Rules\Password; class UserInviteController extends Controller @@ -66,7 +67,7 @@ class UserInviteController extends Controller } $user = $this->userRepo->getById($userId); - $user->password = bcrypt($request->get('password')); + $user->password = Hash::make($request->get('password')); $user->email_confirmed = true; $user->save(); diff --git a/composer.json b/composer.json index 64630833d..3306d3da5 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,6 @@ "laravel/framework": "^8.68", "laravel/socialite": "^5.2", "laravel/tinker": "^2.6", - "laravel/ui": "^3.3", "league/commonmark": "^1.6", "league/flysystem-aws-s3-v3": "^1.0.29", "league/html-to-markdown": "^5.0.0", diff --git a/composer.lock b/composer.lock index b807fd577..cf7b8f72f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1d3bd88b99d07b5410ee4b245bece28e", + "content-hash": "01795571047babf7ee6372b7f98843af", "packages": [ { "name": "aws/aws-crt-php", @@ -2160,67 +2160,6 @@ }, "time": "2022-03-23T12:38:24+00:00" }, - { - "name": "laravel/ui", - "version": "v3.4.6", - "source": { - "type": "git", - "url": "https://github.com/laravel/ui.git", - "reference": "65ec5c03f7fee2c8ecae785795b829a15be48c2c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laravel/ui/zipball/65ec5c03f7fee2c8ecae785795b829a15be48c2c", - "reference": "65ec5c03f7fee2c8ecae785795b829a15be48c2c", - "shasum": "" - }, - "require": { - "illuminate/console": "^8.42|^9.0", - "illuminate/filesystem": "^8.42|^9.0", - "illuminate/support": "^8.82|^9.0", - "illuminate/validation": "^8.42|^9.0", - "php": "^7.3|^8.0" - }, - "require-dev": { - "orchestra/testbench": "^6.23|^7.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - }, - "laravel": { - "providers": [ - "Laravel\\Ui\\UiServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "Laravel\\Ui\\": "src/", - "Illuminate\\Foundation\\Auth\\": "auth-backend/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" - } - ], - "description": "Laravel UI utilities and presets.", - "keywords": [ - "laravel", - "ui" - ], - "support": { - "source": "https://github.com/laravel/ui/tree/v3.4.6" - }, - "time": "2022-05-20T13:38:08+00:00" - }, { "name": "league/commonmark", "version": "1.6.7", From 90b4257889a5f9a63ee5d9934e90557e67ebca56 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 22 Sep 2022 17:15:15 +0100 Subject: [PATCH 03/55] Split out registration and pw-reset tests methods --- tests/Auth/AuthTest.php | 260 ------------------------------- tests/Auth/RegistrationTest.php | 177 +++++++++++++++++++++ tests/Auth/ResetPasswordTest.php | 101 ++++++++++++ 3 files changed, 278 insertions(+), 260 deletions(-) create mode 100644 tests/Auth/RegistrationTest.php create mode 100644 tests/Auth/ResetPasswordTest.php diff --git a/tests/Auth/AuthTest.php b/tests/Auth/AuthTest.php index 106b71875..f0b473472 100644 --- a/tests/Auth/AuthTest.php +++ b/tests/Auth/AuthTest.php @@ -3,13 +3,7 @@ namespace Tests\Auth; use BookStack\Auth\Access\Mfa\MfaSession; -use BookStack\Auth\Role; -use BookStack\Auth\User; use BookStack\Entities\Models\Page; -use BookStack\Notifications\ConfirmEmail; -use BookStack\Notifications\ResetPassword; -use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\Notification; use Illuminate\Testing\TestResponse; use Tests\TestCase; @@ -33,68 +27,6 @@ class AuthTest extends TestCase ->assertSee('Log in'); } - public function test_registration_showing() - { - // Ensure registration form is showing - $this->setSettings(['registration-enabled' => 'true']); - $resp = $this->get('/login'); - $this->withHtml($resp)->assertElementContains('a[href="' . url('/register') . '"]', 'Sign up'); - } - - public function test_normal_registration() - { - // Set settings and get user instance - /** @var Role $registrationRole */ - $registrationRole = Role::query()->first(); - $this->setSettings(['registration-enabled' => 'true', 'registration-role' => $registrationRole->id]); - /** @var User $user */ - $user = User::factory()->make(); - - // Test form and ensure user is created - $resp = $this->get('/register') - ->assertSee('Sign Up'); - $this->withHtml($resp)->assertElementContains('form[action="' . url('/register') . '"]', 'Create Account'); - - $resp = $this->post('/register', $user->only('password', 'name', 'email')); - $resp->assertRedirect('/'); - - $resp = $this->get('/'); - $resp->assertOk(); - $resp->assertSee($user->name); - - $this->assertDatabaseHas('users', ['name' => $user->name, 'email' => $user->email]); - - $user = User::query()->where('email', '=', $user->email)->first(); - $this->assertEquals(1, $user->roles()->count()); - $this->assertEquals($registrationRole->id, $user->roles()->first()->id); - } - - public function test_empty_registration_redirects_back_with_errors() - { - // Set settings and get user instance - $this->setSettings(['registration-enabled' => 'true']); - - // Test form and ensure user is created - $this->get('/register'); - $this->post('/register', [])->assertRedirect('/register'); - $this->get('/register')->assertSee('The name field is required'); - } - - public function test_registration_validation() - { - $this->setSettings(['registration-enabled' => 'true']); - - $this->get('/register'); - $resp = $this->followingRedirects()->post('/register', [ - 'name' => '1', - 'email' => '1', - 'password' => '1', - ]); - $resp->assertSee('The name must be at least 2 characters.'); - $resp->assertSee('The email must be a valid email address.'); - $resp->assertSee('The password must be at least 8 characters.'); - } - public function test_sign_up_link_on_login() { $this->get('/login')->assertDontSee('Sign up'); @@ -104,108 +36,6 @@ class AuthTest extends TestCase $this->get('/login')->assertSee('Sign up'); } - public function test_confirmed_registration() - { - // Fake notifications - Notification::fake(); - - // Set settings and get user instance - $this->setSettings(['registration-enabled' => 'true', 'registration-confirmation' => 'true']); - $user = User::factory()->make(); - - // Go through registration process - $resp = $this->post('/register', $user->only('name', 'email', 'password')); - $resp->assertRedirect('/register/confirm'); - $this->assertDatabaseHas('users', ['name' => $user->name, 'email' => $user->email, 'email_confirmed' => false]); - - // Ensure notification sent - /** @var User $dbUser */ - $dbUser = User::query()->where('email', '=', $user->email)->first(); - Notification::assertSentTo($dbUser, ConfirmEmail::class); - - // Test access and resend confirmation email - $resp = $this->login($user->email, $user->password); - $resp->assertRedirect('/register/confirm/awaiting'); - - $resp = $this->get('/register/confirm/awaiting'); - $this->withHtml($resp)->assertElementContains('form[action="' . url('/register/confirm/resend') . '"]', 'Resend'); - - $this->get('/books')->assertRedirect('/login'); - $this->post('/register/confirm/resend', $user->only('email')); - - // Get confirmation and confirm notification matches - $emailConfirmation = DB::table('email_confirmations')->where('user_id', '=', $dbUser->id)->first(); - Notification::assertSentTo($dbUser, ConfirmEmail::class, function ($notification, $channels) use ($emailConfirmation) { - return $notification->token === $emailConfirmation->token; - }); - - // Check confirmation email confirmation activation. - $this->get('/register/confirm/' . $emailConfirmation->token)->assertRedirect('/login'); - $this->get('/login')->assertSee('Your email has been confirmed! You should now be able to login using this email address.'); - $this->assertDatabaseMissing('email_confirmations', ['token' => $emailConfirmation->token]); - $this->assertDatabaseHas('users', ['name' => $dbUser->name, 'email' => $dbUser->email, 'email_confirmed' => true]); - } - - public function test_restricted_registration() - { - $this->setSettings(['registration-enabled' => 'true', 'registration-confirmation' => 'true', 'registration-restrict' => 'example.com']); - $user = User::factory()->make(); - - // Go through registration process - $this->post('/register', $user->only('name', 'email', 'password')) - ->assertRedirect('/register'); - $resp = $this->get('/register'); - $resp->assertSee('That email domain does not have access to this application'); - $this->assertDatabaseMissing('users', $user->only('email')); - - $user->email = 'barry@example.com'; - - $this->post('/register', $user->only('name', 'email', 'password')) - ->assertRedirect('/register/confirm'); - $this->assertDatabaseHas('users', ['name' => $user->name, 'email' => $user->email, 'email_confirmed' => false]); - - $this->assertNull(auth()->user()); - - $this->get('/')->assertRedirect('/login'); - $resp = $this->followingRedirects()->post('/login', $user->only('email', 'password')); - $resp->assertSee('Email Address Not Confirmed'); - $this->assertNull(auth()->user()); - } - - public function test_restricted_registration_with_confirmation_disabled() - { - $this->setSettings(['registration-enabled' => 'true', 'registration-confirmation' => 'false', 'registration-restrict' => 'example.com']); - $user = User::factory()->make(); - - // Go through registration process - $this->post('/register', $user->only('name', 'email', 'password')) - ->assertRedirect('/register'); - $this->assertDatabaseMissing('users', $user->only('email')); - $this->get('/register')->assertSee('That email domain does not have access to this application'); - - $user->email = 'barry@example.com'; - - $this->post('/register', $user->only('name', 'email', 'password')) - ->assertRedirect('/register/confirm'); - $this->assertDatabaseHas('users', ['name' => $user->name, 'email' => $user->email, 'email_confirmed' => false]); - - $this->assertNull(auth()->user()); - - $this->get('/')->assertRedirect('/login'); - $resp = $this->post('/login', $user->only('email', 'password')); - $resp->assertRedirect('/register/confirm/awaiting'); - $this->get('/register/confirm/awaiting')->assertSee('Email Address Not Confirmed'); - $this->assertNull(auth()->user()); - } - - public function test_registration_role_unset_by_default() - { - $this->assertFalse(setting('registration-role')); - - $resp = $this->asAdmin()->get('/settings/registration'); - $this->withHtml($resp)->assertElementContains('select[name="setting-registration-role"] option[value="0"][selected]', '-- None --'); - } - public function test_logout() { $this->asAdmin()->get('/')->assertOk(); @@ -225,96 +55,6 @@ class AuthTest extends TestCase $this->assertFalse($mfaSession->isVerifiedForUser($user)); } - public function test_reset_password_flow() - { - Notification::fake(); - - $resp = $this->get('/login'); - $this->withHtml($resp)->assertElementContains('a[href="' . url('/password/email') . '"]', 'Forgot Password?'); - - $resp = $this->get('/password/email'); - $this->withHtml($resp)->assertElementContains('form[action="' . url('/password/email') . '"]', 'Send Reset Link'); - - $resp = $this->post('/password/email', [ - 'email' => 'admin@admin.com', - ]); - $resp->assertRedirect('/password/email'); - - $resp = $this->get('/password/email'); - $resp->assertSee('A password reset link will be sent to admin@admin.com if that email address is found in the system.'); - - $this->assertDatabaseHas('password_resets', [ - 'email' => 'admin@admin.com', - ]); - - /** @var User $user */ - $user = User::query()->where('email', '=', 'admin@admin.com')->first(); - - Notification::assertSentTo($user, ResetPassword::class); - $n = Notification::sent($user, ResetPassword::class); - - $this->get('/password/reset/' . $n->first()->token) - ->assertOk() - ->assertSee('Reset Password'); - - $resp = $this->post('/password/reset', [ - 'email' => 'admin@admin.com', - 'password' => 'randompass', - 'password_confirmation' => 'randompass', - 'token' => $n->first()->token, - ]); - $resp->assertRedirect('/'); - - $this->get('/')->assertSee('Your password has been successfully reset'); - } - - public function test_reset_password_flow_shows_success_message_even_if_wrong_password_to_prevent_user_discovery() - { - $this->get('/password/email'); - $resp = $this->followingRedirects()->post('/password/email', [ - 'email' => 'barry@admin.com', - ]); - $resp->assertSee('A password reset link will be sent to barry@admin.com if that email address is found in the system.'); - $resp->assertDontSee('We can\'t find a user'); - - $this->get('/password/reset/arandometokenvalue')->assertSee('Reset Password'); - $resp = $this->post('/password/reset', [ - 'email' => 'barry@admin.com', - 'password' => 'randompass', - 'password_confirmation' => 'randompass', - 'token' => 'arandometokenvalue', - ]); - $resp->assertRedirect('/password/reset/arandometokenvalue'); - - $this->get('/password/reset/arandometokenvalue') - ->assertDontSee('We can\'t find a user') - ->assertSee('The password reset token is invalid for this email address.'); - } - - public function test_reset_password_page_shows_sign_links() - { - $this->setSettings(['registration-enabled' => 'true']); - $resp = $this->get('/password/email'); - $this->withHtml($resp)->assertElementContains('a', 'Log in') - ->assertElementContains('a', 'Sign up'); - } - - public function test_reset_password_request_is_throttled() - { - $editor = $this->getEditor(); - Notification::fake(); - $this->get('/password/email'); - $this->followingRedirects()->post('/password/email', [ - 'email' => $editor->email, - ]); - - $resp = $this->followingRedirects()->post('/password/email', [ - 'email' => $editor->email, - ]); - Notification::assertTimesSent(1, ResetPassword::class); - $resp->assertSee('A password reset link will be sent to ' . $editor->email . ' if that email address is found in the system.'); - } - public function test_login_redirects_to_initially_requested_url_correctly() { config()->set('app.url', 'http://localhost'); diff --git a/tests/Auth/RegistrationTest.php b/tests/Auth/RegistrationTest.php new file mode 100644 index 000000000..45d265b72 --- /dev/null +++ b/tests/Auth/RegistrationTest.php @@ -0,0 +1,177 @@ +setSettings(['registration-enabled' => 'true', 'registration-confirmation' => 'true']); + $user = User::factory()->make(); + + // Go through registration process + $resp = $this->post('/register', $user->only('name', 'email', 'password')); + $resp->assertRedirect('/register/confirm'); + $this->assertDatabaseHas('users', ['name' => $user->name, 'email' => $user->email, 'email_confirmed' => false]); + + // Ensure notification sent + /** @var User $dbUser */ + $dbUser = User::query()->where('email', '=', $user->email)->first(); + Notification::assertSentTo($dbUser, ConfirmEmail::class); + + // Test access and resend confirmation email + $resp = $this->post('/login', ['email' => $user->email, 'password' => $user->password]); + $resp->assertRedirect('/register/confirm/awaiting'); + + $resp = $this->get('/register/confirm/awaiting'); + $this->withHtml($resp)->assertElementContains('form[action="' . url('/register/confirm/resend') . '"]', 'Resend'); + + $this->get('/books')->assertRedirect('/login'); + $this->post('/register/confirm/resend', $user->only('email')); + + // Get confirmation and confirm notification matches + $emailConfirmation = DB::table('email_confirmations')->where('user_id', '=', $dbUser->id)->first(); + Notification::assertSentTo($dbUser, ConfirmEmail::class, function ($notification, $channels) use ($emailConfirmation) { + return $notification->token === $emailConfirmation->token; + }); + + // Check confirmation email confirmation activation. + $this->get('/register/confirm/' . $emailConfirmation->token)->assertRedirect('/login'); + $this->get('/login')->assertSee('Your email has been confirmed! You should now be able to login using this email address.'); + $this->assertDatabaseMissing('email_confirmations', ['token' => $emailConfirmation->token]); + $this->assertDatabaseHas('users', ['name' => $dbUser->name, 'email' => $dbUser->email, 'email_confirmed' => true]); + } + + public function test_restricted_registration() + { + $this->setSettings(['registration-enabled' => 'true', 'registration-confirmation' => 'true', 'registration-restrict' => 'example.com']); + $user = User::factory()->make(); + + // Go through registration process + $this->post('/register', $user->only('name', 'email', 'password')) + ->assertRedirect('/register'); + $resp = $this->get('/register'); + $resp->assertSee('That email domain does not have access to this application'); + $this->assertDatabaseMissing('users', $user->only('email')); + + $user->email = 'barry@example.com'; + + $this->post('/register', $user->only('name', 'email', 'password')) + ->assertRedirect('/register/confirm'); + $this->assertDatabaseHas('users', ['name' => $user->name, 'email' => $user->email, 'email_confirmed' => false]); + + $this->assertNull(auth()->user()); + + $this->get('/')->assertRedirect('/login'); + $resp = $this->followingRedirects()->post('/login', $user->only('email', 'password')); + $resp->assertSee('Email Address Not Confirmed'); + $this->assertNull(auth()->user()); + } + + public function test_restricted_registration_with_confirmation_disabled() + { + $this->setSettings(['registration-enabled' => 'true', 'registration-confirmation' => 'false', 'registration-restrict' => 'example.com']); + $user = User::factory()->make(); + + // Go through registration process + $this->post('/register', $user->only('name', 'email', 'password')) + ->assertRedirect('/register'); + $this->assertDatabaseMissing('users', $user->only('email')); + $this->get('/register')->assertSee('That email domain does not have access to this application'); + + $user->email = 'barry@example.com'; + + $this->post('/register', $user->only('name', 'email', 'password')) + ->assertRedirect('/register/confirm'); + $this->assertDatabaseHas('users', ['name' => $user->name, 'email' => $user->email, 'email_confirmed' => false]); + + $this->assertNull(auth()->user()); + + $this->get('/')->assertRedirect('/login'); + $resp = $this->post('/login', $user->only('email', 'password')); + $resp->assertRedirect('/register/confirm/awaiting'); + $this->get('/register/confirm/awaiting')->assertSee('Email Address Not Confirmed'); + $this->assertNull(auth()->user()); + } + + public function test_registration_role_unset_by_default() + { + $this->assertFalse(setting('registration-role')); + + $resp = $this->asAdmin()->get('/settings/registration'); + $this->withHtml($resp)->assertElementContains('select[name="setting-registration-role"] option[value="0"][selected]', '-- None --'); + } + + public function test_registration_showing() + { + // Ensure registration form is showing + $this->setSettings(['registration-enabled' => 'true']); + $resp = $this->get('/login'); + $this->withHtml($resp)->assertElementContains('a[href="' . url('/register') . '"]', 'Sign up'); + } + + public function test_normal_registration() + { + // Set settings and get user instance + /** @var Role $registrationRole */ + $registrationRole = Role::query()->first(); + $this->setSettings(['registration-enabled' => 'true', 'registration-role' => $registrationRole->id]); + /** @var User $user */ + $user = User::factory()->make(); + + // Test form and ensure user is created + $resp = $this->get('/register') + ->assertSee('Sign Up'); + $this->withHtml($resp)->assertElementContains('form[action="' . url('/register') . '"]', 'Create Account'); + + $resp = $this->post('/register', $user->only('password', 'name', 'email')); + $resp->assertRedirect('/'); + + $resp = $this->get('/'); + $resp->assertOk(); + $resp->assertSee($user->name); + + $this->assertDatabaseHas('users', ['name' => $user->name, 'email' => $user->email]); + + $user = User::query()->where('email', '=', $user->email)->first(); + $this->assertEquals(1, $user->roles()->count()); + $this->assertEquals($registrationRole->id, $user->roles()->first()->id); + } + + public function test_empty_registration_redirects_back_with_errors() + { + // Set settings and get user instance + $this->setSettings(['registration-enabled' => 'true']); + + // Test form and ensure user is created + $this->get('/register'); + $this->post('/register', [])->assertRedirect('/register'); + $this->get('/register')->assertSee('The name field is required'); + } + + public function test_registration_validation() + { + $this->setSettings(['registration-enabled' => 'true']); + + $this->get('/register'); + $resp = $this->followingRedirects()->post('/register', [ + 'name' => '1', + 'email' => '1', + 'password' => '1', + ]); + $resp->assertSee('The name must be at least 2 characters.'); + $resp->assertSee('The email must be a valid email address.'); + $resp->assertSee('The password must be at least 8 characters.'); + } +} diff --git a/tests/Auth/ResetPasswordTest.php b/tests/Auth/ResetPasswordTest.php new file mode 100644 index 000000000..1ab579b26 --- /dev/null +++ b/tests/Auth/ResetPasswordTest.php @@ -0,0 +1,101 @@ +get('/login'); + $this->withHtml($resp)->assertElementContains('a[href="' . url('/password/email') . '"]', 'Forgot Password?'); + + $resp = $this->get('/password/email'); + $this->withHtml($resp)->assertElementContains('form[action="' . url('/password/email') . '"]', 'Send Reset Link'); + + $resp = $this->post('/password/email', [ + 'email' => 'admin@admin.com', + ]); + $resp->assertRedirect('/password/email'); + + $resp = $this->get('/password/email'); + $resp->assertSee('A password reset link will be sent to admin@admin.com if that email address is found in the system.'); + + $this->assertDatabaseHas('password_resets', [ + 'email' => 'admin@admin.com', + ]); + + /** @var User $user */ + $user = User::query()->where('email', '=', 'admin@admin.com')->first(); + + Notification::assertSentTo($user, ResetPassword::class); + $n = Notification::sent($user, ResetPassword::class); + + $this->get('/password/reset/' . $n->first()->token) + ->assertOk() + ->assertSee('Reset Password'); + + $resp = $this->post('/password/reset', [ + 'email' => 'admin@admin.com', + 'password' => 'randompass', + 'password_confirmation' => 'randompass', + 'token' => $n->first()->token, + ]); + $resp->assertRedirect('/'); + + $this->get('/')->assertSee('Your password has been successfully reset'); + } + + public function test_reset_password_flow_shows_success_message_even_if_wrong_password_to_prevent_user_discovery() + { + $this->get('/password/email'); + $resp = $this->followingRedirects()->post('/password/email', [ + 'email' => 'barry@admin.com', + ]); + $resp->assertSee('A password reset link will be sent to barry@admin.com if that email address is found in the system.'); + $resp->assertDontSee('We can\'t find a user'); + + $this->get('/password/reset/arandometokenvalue')->assertSee('Reset Password'); + $resp = $this->post('/password/reset', [ + 'email' => 'barry@admin.com', + 'password' => 'randompass', + 'password_confirmation' => 'randompass', + 'token' => 'arandometokenvalue', + ]); + $resp->assertRedirect('/password/reset/arandometokenvalue'); + + $this->get('/password/reset/arandometokenvalue') + ->assertDontSee('We can\'t find a user') + ->assertSee('The password reset token is invalid for this email address.'); + } + + public function test_reset_password_page_shows_sign_links() + { + $this->setSettings(['registration-enabled' => 'true']); + $resp = $this->get('/password/email'); + $this->withHtml($resp)->assertElementContains('a', 'Log in') + ->assertElementContains('a', 'Sign up'); + } + + public function test_reset_password_request_is_throttled() + { + $editor = $this->getEditor(); + Notification::fake(); + $this->get('/password/email'); + $this->followingRedirects()->post('/password/email', [ + 'email' => $editor->email, + ]); + + $resp = $this->followingRedirects()->post('/password/email', [ + 'email' => $editor->email, + ]); + Notification::assertTimesSent(1, ResetPassword::class); + $resp->assertSee('A password reset link will be sent to ' . $editor->email . ' if that email address is found in the system.'); + } +} From 5c5ea642285751c322d9cf384950a5c7d6cd851f Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 22 Sep 2022 17:29:38 +0100 Subject: [PATCH 04/55] Added login throttling test, updated reset-pw test method names --- tests/Auth/AuthTest.php | 13 +++++++++++++ tests/Auth/ResetPasswordTest.php | 8 ++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/tests/Auth/AuthTest.php b/tests/Auth/AuthTest.php index f0b473472..849469766 100644 --- a/tests/Auth/AuthTest.php +++ b/tests/Auth/AuthTest.php @@ -133,6 +133,19 @@ class AuthTest extends TestCase $this->assertFalse(auth()->check()); } + public function test_login_attempts_are_rate_limited() + { + for ($i = 0; $i < 5; $i++) { + $resp = $this->login('bennynotexisting@example.com', 'pw123'); + } + $resp = $this->followRedirects($resp); + $resp->assertSee('These credentials do not match our records.'); + + // Check the fifth attempt provides a lockout response + $resp = $this->followRedirects($this->login('bennynotexisting@example.com', 'pw123')); + $resp->assertSee('Too many login attempts. Please try again in'); + } + /** * Perform a login. */ diff --git a/tests/Auth/ResetPasswordTest.php b/tests/Auth/ResetPasswordTest.php index 1ab579b26..7b2d2e72b 100644 --- a/tests/Auth/ResetPasswordTest.php +++ b/tests/Auth/ResetPasswordTest.php @@ -9,7 +9,7 @@ use Tests\TestCase; class ResetPasswordTest extends TestCase { - public function test_reset_password_flow() + public function test_reset_flow() { Notification::fake(); @@ -52,7 +52,7 @@ class ResetPasswordTest extends TestCase $this->get('/')->assertSee('Your password has been successfully reset'); } - public function test_reset_password_flow_shows_success_message_even_if_wrong_password_to_prevent_user_discovery() + public function test_reset_flow_shows_success_message_even_if_wrong_password_to_prevent_user_discovery() { $this->get('/password/email'); $resp = $this->followingRedirects()->post('/password/email', [ @@ -75,7 +75,7 @@ class ResetPasswordTest extends TestCase ->assertSee('The password reset token is invalid for this email address.'); } - public function test_reset_password_page_shows_sign_links() + public function test_reset_page_shows_sign_links() { $this->setSettings(['registration-enabled' => 'true']); $resp = $this->get('/password/email'); @@ -83,7 +83,7 @@ class ResetPasswordTest extends TestCase ->assertElementContains('a', 'Sign up'); } - public function test_reset_password_request_is_throttled() + public function test_reset_request_is_throttled() { $editor = $this->getEditor(); Notification::fake(); From e18033ec1ae181a8977d23d14090d0706f3cc05b Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 26 Sep 2022 21:25:32 +0100 Subject: [PATCH 05/55] Added initial support for parallel testing --- app/Providers/AppServiceProvider.php | 7 +++++++ composer.json | 1 + resources/js/wysiwyg/config.js | 1 + tests/TestCase.php | 8 ++++++++ tests/ThemeTest.php | 12 ++++++------ 5 files changed, 23 insertions(+), 6 deletions(-) diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 3c1212e32..02c545db2 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -17,7 +17,9 @@ use GuzzleHttp\Client; use Illuminate\Contracts\Cache\Repository; use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Pagination\Paginator; +use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Blade; +use Illuminate\Support\Facades\ParallelTesting; use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\URL; use Illuminate\Support\Facades\View; @@ -64,6 +66,11 @@ class AppServiceProvider extends ServiceProvider // Set paginator to use bootstrap-style pagination Paginator::useBootstrap(); + + // Setup database upon parallel testing database creation + ParallelTesting::setUpTestDatabase(function ($database, $token) { + Artisan::call('db:seed --class=DummyContentSeeder'); + }); } /** diff --git a/composer.json b/composer.json index 64630833d..44bbf2b99 100644 --- a/composer.json +++ b/composer.json @@ -44,6 +44,7 @@ "ssddanbrown/htmldiff": "^1.0.2" }, "require-dev": { + "brianium/paratest": "^6.6", "fakerphp/faker": "^1.16", "itsgoingd/clockwork": "^5.1", "mockery/mockery": "^1.4", diff --git a/resources/js/wysiwyg/config.js b/resources/js/wysiwyg/config.js index 52c52592c..d2f813cfb 100644 --- a/resources/js/wysiwyg/config.js +++ b/resources/js/wysiwyg/config.js @@ -252,6 +252,7 @@ export function build(options) { document_base_url: window.baseUrl('/'), end_container_on_empty_block: true, remove_trailing_brs: false, + keep_styles: false, statusbar: false, menubar: false, paste_data_images: false, diff --git a/tests/TestCase.php b/tests/TestCase.php index f17d27a1a..0926b0dcc 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -26,6 +26,7 @@ use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\TestCase as BaseTestCase; use Illuminate\Http\JsonResponse; use Illuminate\Support\Env; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; use Illuminate\Testing\Assert as PHPUnit; use Monolog\Handler\TestHandler; @@ -299,6 +300,8 @@ abstract class TestCase extends BaseTestCase /** * Run a set test with the given env variable. * Remembers the original and resets the value after test. + * Database config is juggled so the value can be restored when + * parallel testing are used, where multiple databases exist. */ protected function runWithEnv(string $name, $value, callable $callback) { @@ -311,7 +314,12 @@ abstract class TestCase extends BaseTestCase $_SERVER[$name] = $value; } + $database = config('database.connections.mysql_testing.database'); $this->refreshApplication(); + + DB::purge(); + config()->set('database.connections.mysql_testing.database', $database); + $callback(); if (is_null($originalVal)) { diff --git a/tests/ThemeTest.php b/tests/ThemeTest.php index e83758a95..ac4b35de2 100644 --- a/tests/ThemeTest.php +++ b/tests/ThemeTest.php @@ -322,8 +322,8 @@ class ThemeTest extends TestCase public function test_export_body_start_and_end_template_files_can_be_used() { - $bodyStartStr = 'barry-fought-against-the-panther'; - $bodyEndStr = 'barry-lost-his-fight-with-grace'; + $bodyStartStr = 'garry-fought-against-the-panther'; + $bodyEndStr = 'garry-lost-his-fight-with-grace'; /** @var Page $page */ $page = Page::query()->first(); @@ -342,18 +342,18 @@ class ThemeTest extends TestCase protected function usingThemeFolder(callable $callback) { // Create a folder and configure a theme - $themeFolderName = 'testing_theme_' . rtrim(base64_encode(time()), '='); + $themeFolderName = 'testing_theme_' . str_shuffle(rtrim(base64_encode(time()), '=')); config()->set('view.theme', $themeFolderName); $themeFolderPath = theme_path(''); + + // Create theme folder and clean it up on application tear-down File::makeDirectory($themeFolderPath); + $this->beforeApplicationDestroyed(fn() => File::deleteDirectory($themeFolderPath)); // Run provided callback with theme env option set $this->runWithEnv('APP_THEME', $themeFolderName, function () use ($callback, $themeFolderName) { call_user_func($callback, $themeFolderName); }); - - // Cleanup the custom theme folder we created - File::deleteDirectory($themeFolderPath); } } From f21669c0c966f3dadeac2024a382b8a7cd831a8a Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 27 Sep 2022 01:27:51 +0100 Subject: [PATCH 06/55] Cleaned testing service provider usage Moved testing content out of AppServiceProvider, to a testing-specific service provider. Updated docs and added composer commands to support parallel testing. Also reverted unintentional change to wysiwyg/config.js. --- app/Providers/AppServiceProvider.php | 7 ------- composer.json | 2 ++ readme.md | 9 ++------- resources/js/wysiwyg/config.js | 1 - tests/TestCase.php | 16 ++++++++++++++++ tests/TestServiceProvider.php | 26 ++++++++++++++++++++++++++ 6 files changed, 46 insertions(+), 15 deletions(-) create mode 100644 tests/TestServiceProvider.php diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 02c545db2..3c1212e32 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -17,9 +17,7 @@ use GuzzleHttp\Client; use Illuminate\Contracts\Cache\Repository; use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Pagination\Paginator; -use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Blade; -use Illuminate\Support\Facades\ParallelTesting; use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\URL; use Illuminate\Support\Facades\View; @@ -66,11 +64,6 @@ class AppServiceProvider extends ServiceProvider // Set paginator to use bootstrap-style pagination Paginator::useBootstrap(); - - // Setup database upon parallel testing database creation - ParallelTesting::setUpTestDatabase(function ($database, $token) { - Artisan::call('db:seed --class=DummyContentSeeder'); - }); } /** diff --git a/composer.json b/composer.json index 44bbf2b99..81896f8f8 100644 --- a/composer.json +++ b/composer.json @@ -74,6 +74,8 @@ "format": "phpcbf", "lint": "phpcs", "test": "phpunit", + "t": "@php artisan test --parallel", + "t-reset": "@php artisan test --recreate-databases", "post-autoload-dump": [ "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", "@php artisan package:discover --ansi" diff --git a/readme.md b/readme.md index d0ae1b4f6..16992341d 100644 --- a/readme.md +++ b/readme.md @@ -108,14 +108,9 @@ npm run dev BookStack has many integration tests that use Laravel's built-in testing capabilities which makes use of PHPUnit. There is a `mysql_testing` database defined within the app config which is what is used by PHPUnit. This database is set with the database name, user name and password all defined as `bookstack-test`. You will have to create that database and that set of credentials before testing. -The testing database will also need migrating and seeding beforehand. This can be done with the following commands: +The testing database will also need migrating and seeding beforehand. This can be done by running `composer refresh-test-database`. -``` bash -php artisan migrate --database=mysql_testing -php artisan db:seed --class=DummyContentSeeder --database=mysql_testing -``` - -Once done you can run `composer test` in the application root directory to run all tests. +Once done you can run `composer test` in the application root directory to run all tests. Tests can be ran in parallel by running them via `composer t`. This will use Laravel's built-in parallel testing functionality, and attempt to create and seed a database instance for each testing thread. If required these parallel testing instances can be reset, before testing again, by running `composer t-reset`. ### 📜 Code Standards diff --git a/resources/js/wysiwyg/config.js b/resources/js/wysiwyg/config.js index d2f813cfb..52c52592c 100644 --- a/resources/js/wysiwyg/config.js +++ b/resources/js/wysiwyg/config.js @@ -252,7 +252,6 @@ export function build(options) { document_base_url: window.baseUrl('/'), end_container_on_empty_block: true, remove_trailing_brs: false, - keep_styles: false, statusbar: false, menubar: false, paste_data_images: false, diff --git a/tests/TestCase.php b/tests/TestCase.php index 0926b0dcc..594194168 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -22,6 +22,7 @@ use GuzzleHttp\Client; use GuzzleHttp\Handler\MockHandler; use GuzzleHttp\HandlerStack; use GuzzleHttp\Middleware; +use Illuminate\Contracts\Console\Kernel; use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\TestCase as BaseTestCase; use Illuminate\Http\JsonResponse; @@ -48,6 +49,21 @@ abstract class TestCase extends BaseTestCase */ protected string $baseUrl = 'http://localhost'; + /** + * Creates the application. + * + * @return \Illuminate\Foundation\Application + */ + public function createApplication() + { + /** @var \Illuminate\Foundation\Application $app */ + $app = require __DIR__ . '/../bootstrap/app.php'; + $app->register(TestServiceProvider::class); + $app->make(Kernel::class)->bootstrap(); + + return $app; + } + /** * Set the current user context to be an admin. */ diff --git a/tests/TestServiceProvider.php b/tests/TestServiceProvider.php new file mode 100644 index 000000000..9ad48c442 --- /dev/null +++ b/tests/TestServiceProvider.php @@ -0,0 +1,26 @@ + Date: Tue, 27 Sep 2022 02:48:05 +0100 Subject: [PATCH 07/55] Refactored app service providers Removed old pagination provider as url handling now achieved in a better way. Removed unused broadcast service provider. Moved view-based tweaks into specific provider. Reorganised provider config list. --- app/Config/app.php | 19 +++---- app/Providers/AppServiceProvider.php | 55 +++++++------------ app/Providers/AuthServiceProvider.php | 4 +- app/Providers/BroadcastServiceProvider.php | 25 --------- app/Providers/CustomFacadeProvider.php | 36 ------------ app/Providers/EventServiceProvider.php | 2 +- app/Providers/PaginationServiceProvider.php | 35 ------------ app/Providers/ThemeServiceProvider.php | 6 +- ....php => ValidationRuleServiceProvider.php} | 2 +- app/Providers/ViewTweaksServiceProvider.php | 31 +++++++++++ 10 files changed, 65 insertions(+), 150 deletions(-) delete mode 100644 app/Providers/BroadcastServiceProvider.php delete mode 100644 app/Providers/CustomFacadeProvider.php delete mode 100644 app/Providers/PaginationServiceProvider.php rename app/Providers/{CustomValidationServiceProvider.php => ValidationRuleServiceProvider.php} (93%) create mode 100644 app/Providers/ViewTweaksServiceProvider.php diff --git a/app/Config/app.php b/app/Config/app.php index 738aacdbc..98f83fc39 100644 --- a/app/Config/app.php +++ b/app/Config/app.php @@ -114,6 +114,8 @@ return [ Illuminate\Foundation\Providers\FoundationServiceProvider::class, Illuminate\Hashing\HashServiceProvider::class, Illuminate\Mail\MailServiceProvider::class, + Illuminate\Notifications\NotificationServiceProvider::class, + Illuminate\Pagination\PaginationServiceProvider::class, Illuminate\Pipeline\PipelineServiceProvider::class, Illuminate\Queue\QueueServiceProvider::class, Illuminate\Redis\RedisServiceProvider::class, @@ -121,27 +123,22 @@ return [ Illuminate\Session\SessionServiceProvider::class, Illuminate\Validation\ValidationServiceProvider::class, Illuminate\View\ViewServiceProvider::class, - Illuminate\Notifications\NotificationServiceProvider::class, - SocialiteProviders\Manager\ServiceProvider::class, // Third party service providers - Intervention\Image\ImageServiceProvider::class, Barryvdh\DomPDF\ServiceProvider::class, Barryvdh\Snappy\ServiceProvider::class, - - // BookStack replacement service providers (Extends Laravel) - BookStack\Providers\PaginationServiceProvider::class, - BookStack\Providers\TranslationServiceProvider::class, + Intervention\Image\ImageServiceProvider::class, + SocialiteProviders\Manager\ServiceProvider::class, // BookStack custom service providers BookStack\Providers\ThemeServiceProvider::class, - BookStack\Providers\AuthServiceProvider::class, BookStack\Providers\AppServiceProvider::class, - BookStack\Providers\BroadcastServiceProvider::class, + BookStack\Providers\AuthServiceProvider::class, BookStack\Providers\EventServiceProvider::class, BookStack\Providers\RouteServiceProvider::class, - BookStack\Providers\CustomFacadeProvider::class, - BookStack\Providers\CustomValidationServiceProvider::class, + BookStack\Providers\TranslationServiceProvider::class, + BookStack\Providers\ValidationRuleServiceProvider::class, + BookStack\Providers\ViewTweaksServiceProvider::class, ], /* diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 3c1212e32..d0841059b 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,32 +2,44 @@ namespace BookStack\Providers; -use BookStack\Auth\Access\LoginService; +use BookStack\Actions\ActivityLogger; use BookStack\Auth\Access\SocialAuthService; -use BookStack\Entities\BreadcrumbsViewComposer; use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Page; use BookStack\Exceptions\WhoopsBookStackPrettyHandler; -use BookStack\Settings\Setting; use BookStack\Settings\SettingService; use BookStack\Util\CspService; use GuzzleHttp\Client; -use Illuminate\Contracts\Cache\Repository; use Illuminate\Database\Eloquent\Relations\Relation; -use Illuminate\Pagination\Paginator; -use Illuminate\Support\Facades\Blade; use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\URL; -use Illuminate\Support\Facades\View; use Illuminate\Support\ServiceProvider; -use Laravel\Socialite\Contracts\Factory as SocialiteFactory; use Psr\Http\Client\ClientInterface as HttpClientInterface; use Whoops\Handler\HandlerInterface; class AppServiceProvider extends ServiceProvider { + /** + * Custom container bindings to register. + * @var string[] + */ + public $bindings = [ + HandlerInterface::class => WhoopsBookStackPrettyHandler::class, + ]; + + /** + * Custom singleton bindings to register. + * @var string[] + */ + public $singletons = [ + 'activity' => ActivityLogger::class, + SettingService::class => SettingService::class, + SocialAuthService::class => SocialAuthService::class, + CspService::class => CspService::class, + ]; + /** * Bootstrap any application services. * @@ -43,11 +55,6 @@ class AppServiceProvider extends ServiceProvider URL::forceScheme($isHttps ? 'https' : 'http'); } - // Custom blade view directives - Blade::directive('icon', function ($expression) { - return ""; - }); - // Allow longer string lengths after upgrade to utf8mb4 Schema::defaultStringLength(191); @@ -58,12 +65,6 @@ class AppServiceProvider extends ServiceProvider 'chapter' => Chapter::class, 'page' => Page::class, ]); - - // View Composers - View::composer('entities.breadcrumbs', BreadcrumbsViewComposer::class); - - // Set paginator to use bootstrap-style pagination - Paginator::useBootstrap(); } /** @@ -73,22 +74,6 @@ class AppServiceProvider extends ServiceProvider */ public function register() { - $this->app->bind(HandlerInterface::class, function ($app) { - return $app->make(WhoopsBookStackPrettyHandler::class); - }); - - $this->app->singleton(SettingService::class, function ($app) { - return new SettingService($app->make(Setting::class), $app->make(Repository::class)); - }); - - $this->app->singleton(SocialAuthService::class, function ($app) { - return new SocialAuthService($app->make(SocialiteFactory::class), $app->make(LoginService::class)); - }); - - $this->app->singleton(CspService::class, function ($app) { - return new CspService(); - }); - $this->app->bind(HttpClientInterface::class, function ($app) { return new Client([ 'timeout' => 3, diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index a4022cc50..5e16179ab 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -24,9 +24,7 @@ class AuthServiceProvider extends ServiceProvider { // Password Configuration // Changes here must be reflected in ApiDocsGenerate@getValidationAsString. - Password::defaults(function () { - return Password::min(8); - }); + Password::defaults(fn () => Password::min(8)); // Custom guards Auth::extend('api-token', function ($app, $name, array $config) { diff --git a/app/Providers/BroadcastServiceProvider.php b/app/Providers/BroadcastServiceProvider.php deleted file mode 100644 index 69925e945..000000000 --- a/app/Providers/BroadcastServiceProvider.php +++ /dev/null @@ -1,25 +0,0 @@ -id === (int) $userId; -// }); - } -} diff --git a/app/Providers/CustomFacadeProvider.php b/app/Providers/CustomFacadeProvider.php deleted file mode 100644 index 6ba5632e6..000000000 --- a/app/Providers/CustomFacadeProvider.php +++ /dev/null @@ -1,36 +0,0 @@ -app->singleton('activity', function () { - return $this->app->make(ActivityLogger::class); - }); - - $this->app->singleton('theme', function () { - return $this->app->make(ThemeService::class); - }); - } -} diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 659843ce3..0edc7f09c 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -10,7 +10,7 @@ class EventServiceProvider extends ServiceProvider /** * The event listener mappings for the application. * - * @var array + * @var array> */ protected $listen = [ SocialiteWasCalled::class => [ diff --git a/app/Providers/PaginationServiceProvider.php b/app/Providers/PaginationServiceProvider.php deleted file mode 100644 index 416aa5f34..000000000 --- a/app/Providers/PaginationServiceProvider.php +++ /dev/null @@ -1,35 +0,0 @@ -app['view']; - }); - - Paginator::currentPathResolver(function () { - return url($this->app['request']->path()); - }); - - Paginator::currentPageResolver(function ($pageName = 'page') { - $page = $this->app['request']->input($pageName); - - if (filter_var($page, FILTER_VALIDATE_INT) !== false && (int) $page >= 1) { - return $page; - } - - return 1; - }); - } -} diff --git a/app/Providers/ThemeServiceProvider.php b/app/Providers/ThemeServiceProvider.php index 54c83884a..50c4a5d19 100644 --- a/app/Providers/ThemeServiceProvider.php +++ b/app/Providers/ThemeServiceProvider.php @@ -15,9 +15,8 @@ class ThemeServiceProvider extends ServiceProvider */ public function register() { - $this->app->singleton(ThemeService::class, function ($app) { - return new ThemeService(); - }); + // Register the ThemeService as a singleton + $this->app->singleton(ThemeService::class, fn ($app) => new ThemeService()); } /** @@ -27,6 +26,7 @@ class ThemeServiceProvider extends ServiceProvider */ public function boot() { + // Boot up the theme system $themeService = $this->app->make(ThemeService::class); $themeService->readThemeActions(); $themeService->dispatch(ThemeEvents::APP_BOOT, $this->app); diff --git a/app/Providers/CustomValidationServiceProvider.php b/app/Providers/ValidationRuleServiceProvider.php similarity index 93% rename from app/Providers/CustomValidationServiceProvider.php rename to app/Providers/ValidationRuleServiceProvider.php index ac95099cc..928918dc7 100644 --- a/app/Providers/CustomValidationServiceProvider.php +++ b/app/Providers/ValidationRuleServiceProvider.php @@ -6,7 +6,7 @@ use BookStack\Uploads\ImageService; use Illuminate\Support\Facades\Validator; use Illuminate\Support\ServiceProvider; -class CustomValidationServiceProvider extends ServiceProvider +class ValidationRuleServiceProvider extends ServiceProvider { /** * Register our custom validation rules when the application boots. diff --git a/app/Providers/ViewTweaksServiceProvider.php b/app/Providers/ViewTweaksServiceProvider.php new file mode 100644 index 000000000..f1f1554ae --- /dev/null +++ b/app/Providers/ViewTweaksServiceProvider.php @@ -0,0 +1,31 @@ +"; + }); + } +} From b716fd2b8b2820f9e117be0d6d9b29bdc848cc56 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 27 Sep 2022 02:56:13 +0100 Subject: [PATCH 08/55] Updated composer deps, incremented dev version --- composer.lock | 421 ++++++++++++++++++++++++++++++++++---------------- version | 2 +- 2 files changed, 286 insertions(+), 137 deletions(-) diff --git a/composer.lock b/composer.lock index b807fd577..762fb0a36 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1d3bd88b99d07b5410ee4b245bece28e", + "content-hash": "4a0d254197dda8118685ec1a1eb10edf", "packages": [ { "name": "aws/aws-crt-php", @@ -58,16 +58,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.235.1", + "version": "3.236.0", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "2025db05c7dd22ae414857dadd49207f64c2fc74" + "reference": "bff1f1ade00c758ea27f498baee1fa16901e5bfd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/2025db05c7dd22ae414857dadd49207f64c2fc74", - "reference": "2025db05c7dd22ae414857dadd49207f64c2fc74", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/bff1f1ade00c758ea27f498baee1fa16901e5bfd", + "reference": "bff1f1ade00c758ea27f498baee1fa16901e5bfd", "shasum": "" }, "require": { @@ -86,6 +86,7 @@ "aws/aws-php-sns-message-validator": "~1.0", "behat/behat": "~3.0", "composer/composer": "^1.10.22", + "dms/phpunit-arraysubset-asserts": "^0.4.0", "doctrine/cache": "~1.4", "ext-dom": "*", "ext-openssl": "*", @@ -93,10 +94,11 @@ "ext-sockets": "*", "nette/neon": "^2.3", "paragonie/random_compat": ">= 2", - "phpunit/phpunit": "^4.8.35 || ^5.6.3", + "phpunit/phpunit": "^4.8.35 || ^5.6.3 || ^9.5", "psr/cache": "^1.0", "psr/simple-cache": "^1.0", - "sebastian/comparator": "^1.2.3" + "sebastian/comparator": "^1.2.3 || ^4.0", + "yoast/phpunit-polyfills": "^1.0" }, "suggest": { "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", @@ -144,9 +146,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.235.1" + "source": "https://github.com/aws/aws-sdk-php/tree/3.236.0" }, - "time": "2022-09-02T18:18:19+00:00" + "time": "2022-09-26T18:13:07+00:00" }, { "name": "bacon/bacon-qr-code", @@ -559,16 +561,16 @@ }, { "name": "doctrine/dbal", - "version": "3.4.3", + "version": "3.4.5", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "a24b89d663d8f261199bc0a91c48016042ebda85" + "reference": "a5a58773109c0abb13e658c8ccd92aeec8d07f9e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/a24b89d663d8f261199bc0a91c48016042ebda85", - "reference": "a24b89d663d8f261199bc0a91c48016042ebda85", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/a5a58773109c0abb13e658c8ccd92aeec8d07f9e", + "reference": "a5a58773109c0abb13e658c8ccd92aeec8d07f9e", "shasum": "" }, "require": { @@ -583,14 +585,14 @@ "require-dev": { "doctrine/coding-standard": "10.0.0", "jetbrains/phpstorm-stubs": "2022.2", - "phpstan/phpstan": "1.8.2", + "phpstan/phpstan": "1.8.3", "phpstan/phpstan-strict-rules": "^1.3", - "phpunit/phpunit": "9.5.21", + "phpunit/phpunit": "9.5.24", "psalm/plugin-phpunit": "0.17.0", "squizlabs/php_codesniffer": "3.7.1", "symfony/cache": "^5.4|^6.0", "symfony/console": "^4.4|^5.4|^6.0", - "vimeo/psalm": "4.24.0" + "vimeo/psalm": "4.27.0" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." @@ -650,7 +652,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.4.3" + "source": "https://github.com/doctrine/dbal/tree/3.4.5" }, "funding": [ { @@ -666,7 +668,7 @@ "type": "tidelift" } ], - "time": "2022-08-28T17:26:36+00:00" + "time": "2022-09-23T17:48:57+00:00" }, { "name": "doctrine/deprecations", @@ -804,28 +806,28 @@ }, { "name": "doctrine/inflector", - "version": "2.0.4", + "version": "2.0.5", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89" + "reference": "ade2b3bbfb776f27f0558e26eed43b5d9fe1b392" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", - "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/ade2b3bbfb776f27f0558e26eed43b5d9fe1b392", + "reference": "ade2b3bbfb776f27f0558e26eed43b5d9fe1b392", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^8.2", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", - "vimeo/psalm": "^4.10" + "doctrine/coding-standard": "^9", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.3", + "phpunit/phpunit": "^8.5 || ^9.5", + "vimeo/psalm": "^4.25" }, "type": "library", "autoload": { @@ -875,7 +877,7 @@ ], "support": { "issues": "https://github.com/doctrine/inflector/issues", - "source": "https://github.com/doctrine/inflector/tree/2.0.4" + "source": "https://github.com/doctrine/inflector/tree/2.0.5" }, "funding": [ { @@ -891,7 +893,7 @@ "type": "tidelift" } ], - "time": "2021-10-22T20:16:43+00:00" + "time": "2022-09-07T09:01:28+00:00" }, { "name": "doctrine/lexer", @@ -971,24 +973,24 @@ }, { "name": "dompdf/dompdf", - "version": "v2.0.0", + "version": "v2.0.1", "source": { "type": "git", "url": "https://github.com/dompdf/dompdf.git", - "reference": "79573d8b8a141ec8a17312515de8740eed014fa9" + "reference": "c5310df0e22c758c85ea5288175fc6cd777bc085" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dompdf/dompdf/zipball/79573d8b8a141ec8a17312515de8740eed014fa9", - "reference": "79573d8b8a141ec8a17312515de8740eed014fa9", + "url": "https://api.github.com/repos/dompdf/dompdf/zipball/c5310df0e22c758c85ea5288175fc6cd777bc085", + "reference": "c5310df0e22c758c85ea5288175fc6cd777bc085", "shasum": "" }, "require": { "ext-dom": "*", "ext-mbstring": "*", "masterminds/html5": "^2.0", - "phenx/php-font-lib": "^0.5.4", - "phenx/php-svg-lib": "^0.3.3 || ^0.4.0", + "phenx/php-font-lib": ">=0.5.4 <1.0.0", + "phenx/php-svg-lib": ">=0.3.3 <1.0.0", "php": "^7.1 || ^8.0" }, "require-dev": { @@ -1019,38 +1021,30 @@ ], "authors": [ { - "name": "Fabien Ménager", - "email": "fabien.menager@gmail.com" - }, - { - "name": "Brian Sweeney", - "email": "eclecticgeek@gmail.com" - }, - { - "name": "Gabriel Bull", - "email": "me@gabrielbull.com" + "name": "The Dompdf Community", + "homepage": "https://github.com/dompdf/dompdf/blob/master/AUTHORS.md" } ], "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter", "homepage": "https://github.com/dompdf/dompdf", "support": { "issues": "https://github.com/dompdf/dompdf/issues", - "source": "https://github.com/dompdf/dompdf/tree/v2.0.0" + "source": "https://github.com/dompdf/dompdf/tree/v2.0.1" }, - "time": "2022-06-21T21:14:57+00:00" + "time": "2022-09-22T13:43:41+00:00" }, { "name": "dragonmantank/cron-expression", - "version": "v3.3.1", + "version": "v3.3.2", "source": { "type": "git", "url": "https://github.com/dragonmantank/cron-expression.git", - "reference": "be85b3f05b46c39bbc0d95f6c071ddff669510fa" + "reference": "782ca5968ab8b954773518e9e49a6f892a34b2a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/be85b3f05b46c39bbc0d95f6c071ddff669510fa", - "reference": "be85b3f05b46c39bbc0d95f6c071ddff669510fa", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/782ca5968ab8b954773518e9e49a6f892a34b2a8", + "reference": "782ca5968ab8b954773518e9e49a6f892a34b2a8", "shasum": "" }, "require": { @@ -1090,7 +1084,7 @@ ], "support": { "issues": "https://github.com/dragonmantank/cron-expression/issues", - "source": "https://github.com/dragonmantank/cron-expression/tree/v3.3.1" + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.3.2" }, "funding": [ { @@ -1098,7 +1092,7 @@ "type": "github" } ], - "time": "2022-01-18T15:43:28+00:00" + "time": "2022-09-10T18:51:20+00:00" }, { "name": "egulias/email-validator", @@ -1792,16 +1786,16 @@ }, { "name": "laravel/framework", - "version": "v8.83.23", + "version": "v8.83.24", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "bdc707f8b9bcad289b24cd182d98ec7480ac4491" + "reference": "a684da6197ae77eee090637ae4411b2f321adfc7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/bdc707f8b9bcad289b24cd182d98ec7480ac4491", - "reference": "bdc707f8b9bcad289b24cd182d98ec7480ac4491", + "url": "https://api.github.com/repos/laravel/framework/zipball/a684da6197ae77eee090637ae4411b2f321adfc7", + "reference": "a684da6197ae77eee090637ae4411b2f321adfc7", "shasum": "" }, "require": { @@ -1961,20 +1955,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2022-07-26T13:30:00+00:00" + "time": "2022-09-22T18:59:47+00:00" }, { "name": "laravel/serializable-closure", - "version": "v1.2.1", + "version": "v1.2.2", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "d78fd36ba031a1a695ea5a406f29996948d7011b" + "reference": "47afb7fae28ed29057fdca37e16a84f90cc62fae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/d78fd36ba031a1a695ea5a406f29996948d7011b", - "reference": "d78fd36ba031a1a695ea5a406f29996948d7011b", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/47afb7fae28ed29057fdca37e16a84f90cc62fae", + "reference": "47afb7fae28ed29057fdca37e16a84f90cc62fae", "shasum": "" }, "require": { @@ -2021,7 +2015,7 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2022-08-26T15:25:27+00:00" + "time": "2022-09-08T13:45:54+00:00" }, { "name": "laravel/socialite", @@ -3438,21 +3432,21 @@ }, { "name": "phenx/php-svg-lib", - "version": "0.4.1", + "version": "0.5.0", "source": { "type": "git", "url": "https://github.com/dompdf/php-svg-lib.git", - "reference": "4498b5df7b08e8469f0f8279651ea5de9626ed02" + "reference": "76876c6cf3080bcb6f249d7d59705108166a6685" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/4498b5df7b08e8469f0f8279651ea5de9626ed02", - "reference": "4498b5df7b08e8469f0f8279651ea5de9626ed02", + "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/76876c6cf3080bcb6f249d7d59705108166a6685", + "reference": "76876c6cf3080bcb6f249d7d59705108166a6685", "shasum": "" }, "require": { "ext-mbstring": "*", - "php": "^7.1 || ^7.2 || ^7.3 || ^7.4 || ^8.0", + "php": "^7.1 || ^8.0", "sabberworm/php-css-parser": "^8.4" }, "require-dev": { @@ -3478,9 +3472,9 @@ "homepage": "https://github.com/PhenX/php-svg-lib", "support": { "issues": "https://github.com/dompdf/php-svg-lib/issues", - "source": "https://github.com/dompdf/php-svg-lib/tree/0.4.1" + "source": "https://github.com/dompdf/php-svg-lib/tree/0.5.0" }, - "time": "2022-03-07T12:52:04+00:00" + "time": "2022-09-06T12:16:56+00:00" }, { "name": "phpoption/phpoption", @@ -3559,16 +3553,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "3.0.15", + "version": "3.0.16", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "c96e250238e88bf1040e9f7715efab1d6bc7f622" + "reference": "7181378909ed8890be4db53d289faac5b77f8b05" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/c96e250238e88bf1040e9f7715efab1d6bc7f622", - "reference": "c96e250238e88bf1040e9f7715efab1d6bc7f622", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/7181378909ed8890be4db53d289faac5b77f8b05", + "reference": "7181378909ed8890be4db53d289faac5b77f8b05", "shasum": "" }, "require": { @@ -3649,7 +3643,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.15" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.16" }, "funding": [ { @@ -3665,7 +3659,7 @@ "type": "tidelift" } ], - "time": "2022-09-02T17:05:08+00:00" + "time": "2022-09-05T18:03:08+00:00" }, { "name": "pragmarx/google2fa", @@ -7258,16 +7252,16 @@ }, { "name": "tijsverkoyen/css-to-inline-styles", - "version": "2.2.4", + "version": "2.2.5", "source": { "type": "git", "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", - "reference": "da444caae6aca7a19c0c140f68c6182e337d5b1c" + "reference": "4348a3a06651827a27d989ad1d13efec6bb49b19" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/da444caae6aca7a19c0c140f68c6182e337d5b1c", - "reference": "da444caae6aca7a19c0c140f68c6182e337d5b1c", + "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/4348a3a06651827a27d989ad1d13efec6bb49b19", + "reference": "4348a3a06651827a27d989ad1d13efec6bb49b19", "shasum": "" }, "require": { @@ -7305,9 +7299,9 @@ "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", "support": { "issues": "https://github.com/tijsverkoyen/CssToInlineStyles/issues", - "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/2.2.4" + "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/2.2.5" }, - "time": "2021-12-08T09:12:39+00:00" + "time": "2022-09-12T13:28:28+00:00" }, { "name": "vlucas/phpdotenv", @@ -7523,6 +7517,98 @@ } ], "packages-dev": [ + { + "name": "brianium/paratest", + "version": "v6.6.4", + "source": { + "type": "git", + "url": "https://github.com/paratestphp/paratest.git", + "reference": "4ce800dc32fd0292a4f05c00f347142dce1ecdda" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/4ce800dc32fd0292a4f05c00f347142dce1ecdda", + "reference": "4ce800dc32fd0292a4f05c00f347142dce1ecdda", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-simplexml": "*", + "jean85/pretty-package-versions": "^2.0.5", + "php": "^7.3 || ^8.0", + "phpunit/php-code-coverage": "^9.2.17", + "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-timer": "^5.0.3", + "phpunit/phpunit": "^9.5.24", + "sebastian/environment": "^5.1.4", + "symfony/console": "^5.4.12 || ^6.1.4", + "symfony/process": "^5.4.11 || ^6.1.3" + }, + "require-dev": { + "doctrine/coding-standard": "^10.0.0", + "ext-pcov": "*", + "ext-posix": "*", + "infection/infection": "^0.26.14", + "malukenho/mcbumpface": "^1.1.5", + "squizlabs/php_codesniffer": "^3.7.1", + "symfony/filesystem": "^5.4.12 || ^6.1.4", + "vimeo/psalm": "^4.27.0" + }, + "bin": [ + "bin/paratest", + "bin/paratest.bat", + "bin/paratest_for_phpstorm" + ], + "type": "library", + "autoload": { + "psr-4": { + "ParaTest\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Scaturro", + "email": "scaturrob@gmail.com", + "role": "Developer" + }, + { + "name": "Filippo Tessarotto", + "email": "zoeslam@gmail.com", + "role": "Developer" + } + ], + "description": "Parallel testing for PHP", + "homepage": "https://github.com/paratestphp/paratest", + "keywords": [ + "concurrent", + "parallel", + "phpunit", + "testing" + ], + "support": { + "issues": "https://github.com/paratestphp/paratest/issues", + "source": "https://github.com/paratestphp/paratest/tree/v6.6.4" + }, + "funding": [ + { + "url": "https://github.com/sponsors/Slamdunk", + "type": "github" + }, + { + "url": "https://paypal.me/filippotessarotto", + "type": "paypal" + } + ], + "time": "2022-09-13T10:47:01+00:00" + }, { "name": "composer/ca-bundle", "version": "1.3.3", @@ -7674,16 +7760,16 @@ }, { "name": "composer/composer", - "version": "2.4.1", + "version": "2.4.2", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "777d542e3af65f8e7a66a4d98ce7a697da339414" + "reference": "7d887621e69a0311eb50aed4a16f7044b2b385b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/777d542e3af65f8e7a66a4d98ce7a697da339414", - "reference": "777d542e3af65f8e7a66a4d98ce7a697da339414", + "url": "https://api.github.com/repos/composer/composer/zipball/7d887621e69a0311eb50aed4a16f7044b2b385b9", + "reference": "7d887621e69a0311eb50aed4a16f7044b2b385b9", "shasum": "" }, "require": { @@ -7713,7 +7799,7 @@ "phpstan/phpstan-deprecation-rules": "^1", "phpstan/phpstan-phpunit": "^1.0", "phpstan/phpstan-strict-rules": "^1", - "phpstan/phpstan-symfony": "^1.1", + "phpstan/phpstan-symfony": "^1.2.10", "symfony/phpunit-bridge": "^6.0" }, "suggest": { @@ -7766,7 +7852,7 @@ "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/composer/issues", - "source": "https://github.com/composer/composer/tree/2.4.1" + "source": "https://github.com/composer/composer/tree/2.4.2" }, "funding": [ { @@ -7782,7 +7868,7 @@ "type": "tidelift" } ], - "time": "2022-08-20T09:44:50+00:00" + "time": "2022-09-14T14:11:15+00:00" }, { "name": "composer/metadata-minifier", @@ -8394,16 +8480,16 @@ }, { "name": "itsgoingd/clockwork", - "version": "v5.1.7", + "version": "v5.1.8", "source": { "type": "git", "url": "https://github.com/itsgoingd/clockwork.git", - "reference": "2cad6c75dc2b96cbfd48c0511bb035a4e328c17f" + "reference": "74ee05a61296aa7298164ef5346f0a568aa6106e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/itsgoingd/clockwork/zipball/2cad6c75dc2b96cbfd48c0511bb035a4e328c17f", - "reference": "2cad6c75dc2b96cbfd48c0511bb035a4e328c17f", + "url": "https://api.github.com/repos/itsgoingd/clockwork/zipball/74ee05a61296aa7298164ef5346f0a568aa6106e", + "reference": "74ee05a61296aa7298164ef5346f0a568aa6106e", "shasum": "" }, "require": { @@ -8450,7 +8536,7 @@ ], "support": { "issues": "https://github.com/itsgoingd/clockwork/issues", - "source": "https://github.com/itsgoingd/clockwork/tree/v5.1.7" + "source": "https://github.com/itsgoingd/clockwork/tree/v5.1.8" }, "funding": [ { @@ -8458,7 +8544,66 @@ "type": "github" } ], - "time": "2022-08-14T21:23:22+00:00" + "time": "2022-09-25T20:21:14+00:00" + }, + { + "name": "jean85/pretty-package-versions", + "version": "2.0.5", + "source": { + "type": "git", + "url": "https://github.com/Jean85/pretty-package-versions.git", + "reference": "ae547e455a3d8babd07b96966b17d7fd21d9c6af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/ae547e455a3d8babd07b96966b17d7fd21d9c6af", + "reference": "ae547e455a3d8babd07b96966b17d7fd21d9c6af", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.0.0", + "php": "^7.1|^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.17", + "jean85/composer-provided-replaced-stub-package": "^1.0", + "phpstan/phpstan": "^0.12.66", + "phpunit/phpunit": "^7.5|^8.5|^9.4", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Jean85\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alessandro Lai", + "email": "alessandro.lai85@gmail.com" + } + ], + "description": "A library to get pretty versions strings of installed dependencies", + "keywords": [ + "composer", + "package", + "release", + "versions" + ], + "support": { + "issues": "https://github.com/Jean85/pretty-package-versions/issues", + "source": "https://github.com/Jean85/pretty-package-versions/tree/2.0.5" + }, + "time": "2021-10-08T21:21:46+00:00" }, { "name": "justinrainbow/json-schema", @@ -8532,16 +8677,16 @@ }, { "name": "mockery/mockery", - "version": "1.5.0", + "version": "1.5.1", "source": { "type": "git", "url": "https://github.com/mockery/mockery.git", - "reference": "c10a5f6e06fc2470ab1822fa13fa2a7380f8fbac" + "reference": "e92dcc83d5a51851baf5f5591d32cb2b16e3684e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/c10a5f6e06fc2470ab1822fa13fa2a7380f8fbac", - "reference": "c10a5f6e06fc2470ab1822fa13fa2a7380f8fbac", + "url": "https://api.github.com/repos/mockery/mockery/zipball/e92dcc83d5a51851baf5f5591d32cb2b16e3684e", + "reference": "e92dcc83d5a51851baf5f5591d32cb2b16e3684e", "shasum": "" }, "require": { @@ -8598,9 +8743,9 @@ ], "support": { "issues": "https://github.com/mockery/mockery/issues", - "source": "https://github.com/mockery/mockery/tree/1.5.0" + "source": "https://github.com/mockery/mockery/tree/1.5.1" }, - "time": "2022-01-20T13:18:17+00:00" + "time": "2022-09-07T15:32:08+00:00" }, { "name": "myclabs/deep-copy", @@ -8959,16 +9104,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.8.4", + "version": "1.8.6", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "eed4c9da531f6ebb4787235b6fb486e2c20f34e5" + "reference": "c386ab2741e64cc9e21729f891b28b2b10fe6618" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/eed4c9da531f6ebb4787235b6fb486e2c20f34e5", - "reference": "eed4c9da531f6ebb4787235b6fb486e2c20f34e5", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/c386ab2741e64cc9e21729f891b28b2b10fe6618", + "reference": "c386ab2741e64cc9e21729f891b28b2b10fe6618", "shasum": "" }, "require": { @@ -8998,7 +9143,7 @@ ], "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.8.4" + "source": "https://github.com/phpstan/phpstan/tree/1.8.6" }, "funding": [ { @@ -9014,7 +9159,7 @@ "type": "tidelift" } ], - "time": "2022-09-03T13:08:04+00:00" + "time": "2022-09-23T09:54:39+00:00" }, { "name": "phpunit/php-code-coverage", @@ -9336,16 +9481,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.24", + "version": "9.5.25", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "d0aa6097bef9fd42458a9b3c49da32c6ce6129c5" + "reference": "3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d0aa6097bef9fd42458a9b3c49da32c6ce6129c5", - "reference": "d0aa6097bef9fd42458a9b3c49da32c6ce6129c5", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d", + "reference": "3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d", "shasum": "" }, "require": { @@ -9367,14 +9512,14 @@ "phpunit/php-timer": "^5.0.2", "sebastian/cli-parser": "^1.0.1", "sebastian/code-unit": "^1.0.6", - "sebastian/comparator": "^4.0.5", + "sebastian/comparator": "^4.0.8", "sebastian/diff": "^4.0.3", "sebastian/environment": "^5.1.3", - "sebastian/exporter": "^4.0.3", + "sebastian/exporter": "^4.0.5", "sebastian/global-state": "^5.0.1", "sebastian/object-enumerator": "^4.0.3", "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^3.1", + "sebastian/type": "^3.2", "sebastian/version": "^3.0.2" }, "suggest": { @@ -9418,7 +9563,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.24" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.25" }, "funding": [ { @@ -9428,9 +9573,13 @@ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" } ], - "time": "2022-08-30T07:42:16+00:00" + "time": "2022-09-25T03:44:45+00:00" }, { "name": "react/promise", @@ -9677,16 +9826,16 @@ }, { "name": "sebastian/comparator", - "version": "4.0.6", + "version": "4.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "55f4261989e546dc112258c7a75935a81a7ce382" + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382", - "reference": "55f4261989e546dc112258c7a75935a81a7ce382", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", "shasum": "" }, "require": { @@ -9739,7 +9888,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.6" + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" }, "funding": [ { @@ -9747,7 +9896,7 @@ "type": "github" } ], - "time": "2020-10-26T15:49:45+00:00" + "time": "2022-09-14T12:41:17+00:00" }, { "name": "sebastian/complexity", @@ -9937,16 +10086,16 @@ }, { "name": "sebastian/exporter", - "version": "4.0.4", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9" + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/65e8b7db476c5dd267e65eea9cab77584d3cfff9", - "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", "shasum": "" }, "require": { @@ -10002,7 +10151,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.4" + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" }, "funding": [ { @@ -10010,7 +10159,7 @@ "type": "github" } ], - "time": "2021-11-11T14:18:36+00:00" + "time": "2022-09-14T06:03:37+00:00" }, { "name": "sebastian/global-state", @@ -10365,16 +10514,16 @@ }, { "name": "sebastian/type", - "version": "3.1.0", + "version": "3.2.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "fb44e1cc6e557418387ad815780360057e40753e" + "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb44e1cc6e557418387ad815780360057e40753e", - "reference": "fb44e1cc6e557418387ad815780360057e40753e", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", + "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", "shasum": "" }, "require": { @@ -10386,7 +10535,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-master": "3.2-dev" } }, "autoload": { @@ -10409,7 +10558,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.1.0" + "source": "https://github.com/sebastianbergmann/type/tree/3.2.0" }, "funding": [ { @@ -10417,7 +10566,7 @@ "type": "github" } ], - "time": "2022-08-29T06:55:37+00:00" + "time": "2022-09-12T14:47:03+00:00" }, { "name": "sebastian/version", diff --git a/version b/version index 1edf07d8a..3dbb8ff33 100644 --- a/version +++ b/version @@ -1 +1 @@ -v22.07-dev +v22.10-dev From 931641ed2c1a1a85af698f227d56ae96b381e0d3 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 27 Sep 2022 12:23:16 +0100 Subject: [PATCH 09/55] Tweaked license and readme text Updated license copyright line to better help it be detected as MIT by automatic license systems (Such as GitHub license detection) while removing contributors link which would not actually list all contributors. Also added year range back in to be more specific about active lifetime. --- LICENSE | 3 +-- readme.md | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/LICENSE b/LICENSE index 0ec2e91ab..5b3d8699a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015-present, Dan Brown and the BookStack Project contributors -https://github.com/BookStackApp/BookStack/graphs/contributors +Copyright (c) 2015-2022, Dan Brown and the BookStack Project contributors. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/readme.md b/readme.md index 16992341d..469ec88fd 100644 --- a/readme.md +++ b/readme.md @@ -216,16 +216,14 @@ The website which contains the project docs & Blog can be found in the [BookStac ## ⚖️ License -The BookStack source is provided under the MIT License. +The BookStack source is provided under the [MIT License](https://github.com/BookStackApp/BookStack/blob/development/LICENSE). The libraries used by, and included with, BookStack are provided under their own licenses and copyright. The licenses for many of our core dependencies can be found in the attribution list below but this is not an exhaustive list of all projects used within BookStack. ## 👪 Attribution -The great people that have worked to build and improve BookStack can [be seen here](https://github.com/BookStackApp/BookStack/graphs/contributors). - -The wonderful people that have provided translations, either through GitHub or via Crowdin [can be seen here](https://github.com/BookStackApp/BookStack/blob/development/.github/translators.txt). +The great people that have worked to build and improve BookStack can [be seen here](https://github.com/BookStackApp/BookStack/graphs/contributors). The wonderful people that have provided translations, either through GitHub or via Crowdin [can be seen here](https://github.com/BookStackApp/BookStack/blob/development/.github/translators.txt). Below are the great open-source projects used to help build BookStack. Note: This is not an exhaustive list of all libraries and projects that would be used in an active BookStack instance. From af434d021663dc18df90c2ee0639dc9b027842c4 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 27 Sep 2022 18:44:06 +0100 Subject: [PATCH 10/55] Fixed custom code theme not showing in WYSIWYG Fixes #3753 Was caused by not including added styles to the code block shadow root. --- resources/js/wysiwyg/plugin-codeeditor.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/js/wysiwyg/plugin-codeeditor.js b/resources/js/wysiwyg/plugin-codeeditor.js index b9fc355e1..5377f564e 100644 --- a/resources/js/wysiwyg/plugin-codeeditor.js +++ b/resources/js/wysiwyg/plugin-codeeditor.js @@ -39,16 +39,16 @@ function defineCodeBlockCustomElement(editor) { constructor() { super(); this.attachShadow({mode: 'open'}); - const linkElem = document.createElement('link'); - linkElem.setAttribute('rel', 'stylesheet'); - linkElem.setAttribute('href', window.baseUrl('/dist/styles.css')); + + const stylesToCopy = document.querySelectorAll('link[rel="stylesheet"]:not([media="print"])'); + const copiedStyles = Array.from(stylesToCopy).map(styleEl => styleEl.cloneNode(false)); const cmContainer = document.createElement('div'); cmContainer.style.pointerEvents = 'none'; cmContainer.contentEditable = 'false'; cmContainer.classList.add('CodeMirrorContainer'); - this.shadowRoot.append(linkElem, cmContainer); + this.shadowRoot.append(...copiedStyles, cmContainer); } getLanguage() { From 391fb2cc62f19e36d4cb27a204135e0a95189bdb Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 27 Sep 2022 18:52:21 +0100 Subject: [PATCH 11/55] Added MATLAB/Octave code highlighting support --- resources/js/code.mjs | 3 +++ resources/views/pages/parts/code-editor.blade.php | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/resources/js/code.mjs b/resources/js/code.mjs index eca941f1c..5881e2512 100644 --- a/resources/js/code.mjs +++ b/resources/js/code.mjs @@ -15,6 +15,7 @@ import 'codemirror/mode/lua/lua'; import 'codemirror/mode/markdown/markdown'; import 'codemirror/mode/mllike/mllike'; import 'codemirror/mode/nginx/nginx'; +import 'codemirror/mode/octave/octave'; import 'codemirror/mode/perl/perl'; import 'codemirror/mode/pascal/pascal'; import 'codemirror/mode/php/php'; @@ -65,11 +66,13 @@ const modeMap = { julia: 'text/x-julia', latex: 'text/x-stex', lua: 'lua', + matlab: 'text/x-octave', md: 'markdown', mdown: 'markdown', markdown: 'markdown', ml: 'mllike', nginx: 'nginx', + octave: 'text/x-octave', perl: 'perl', pl: 'perl', powershell: 'powershell', diff --git a/resources/views/pages/parts/code-editor.blade.php b/resources/views/pages/parts/code-editor.blade.php index e86282d73..770ed4840 100644 --- a/resources/views/pages/parts/code-editor.blade.php +++ b/resources/views/pages/parts/code-editor.blade.php @@ -24,8 +24,8 @@ @php $languages = [ 'Bash', 'CSS', 'C', 'C++', 'C#', 'Diff', 'Fortran', 'F#', 'Go', 'Haskell', 'HTML', 'INI', - 'Java', 'JavaScript', 'JSON', 'Julia', 'Kotlin', 'LaTeX', 'Lua', 'MarkDown', 'Nginx', 'OCaml', - 'Pascal', 'Perl', 'PHP', 'Powershell', 'Python', 'Ruby', 'Rust', 'Shell', 'SQL', 'TypeScript', + 'Java', 'JavaScript', 'JSON', 'Julia', 'Kotlin', 'LaTeX', 'Lua', 'MarkDown', 'MATLAB', 'Nginx', 'OCaml', + 'Octave', 'Pascal', 'Perl', 'PHP', 'Powershell', 'Python', 'Ruby', 'Rust', 'Shell', 'SQL', 'TypeScript', 'VBScript', 'VB.NET', 'XML', 'YAML', ]; @endphp From d933fe5dcec5189a235058ff7852d3af83bf893f Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 27 Sep 2022 19:05:03 +0100 Subject: [PATCH 12/55] Updated WYSIWYG config to allow styles on list elements --- resources/js/wysiwyg/config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/js/wysiwyg/config.js b/resources/js/wysiwyg/config.js index 52c52592c..8c85f60e2 100644 --- a/resources/js/wysiwyg/config.js +++ b/resources/js/wysiwyg/config.js @@ -255,7 +255,7 @@ export function build(options) { statusbar: false, menubar: false, paste_data_images: false, - extended_valid_elements: 'pre[*],svg[*],div[drawio-diagram],details[*],summary[*],div[*],li[class|checked]', + extended_valid_elements: 'pre[*],svg[*],div[drawio-diagram],details[*],summary[*],div[*],li[class|checked|style]', automatic_uploads: false, custom_elements: 'doc-root,code-block', valid_children: [ From 6dd89ba9560db36dece3b17529f4db55f96c25f5 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 27 Sep 2022 20:11:58 +0100 Subject: [PATCH 13/55] Split out some development-specific readme parts to own pages --- dev/docs/development.md | 96 +++++++++++++++++++++++++++++ dev/docs/release-process.md | 24 ++++++++ readme.md | 116 ++---------------------------------- 3 files changed, 125 insertions(+), 111 deletions(-) create mode 100644 dev/docs/development.md create mode 100644 dev/docs/release-process.md diff --git a/dev/docs/development.md b/dev/docs/development.md new file mode 100644 index 000000000..6d11443b6 --- /dev/null +++ b/dev/docs/development.md @@ -0,0 +1,96 @@ +# Development & Testing + +All development on BookStack is currently done on the `development` branch. +When it's time for a release the `development` branch is merged into release with built & minified CSS & JS then tagged at its version. Here are the current development requirements: + +* [Node.js](https://nodejs.org/en/) v16.0+ + +This project uses SASS for CSS development and this is built, along with the JavaScript, using a range of npm scripts. The below npm commands can be used to install the dependencies & run the build tasks: + +``` bash +# Install NPM Dependencies +npm install + +# Build assets for development +npm run build + +# Build and minify assets for production +npm run production + +# Build for dev (With sourcemaps) and watch for changes +npm run dev +``` + +BookStack has many integration tests that use Laravel's built-in testing capabilities which makes use of PHPUnit. There is a `mysql_testing` database defined within the app config which is what is used by PHPUnit. This database is set with the database name, username and password all defined as `bookstack-test`. You will have to create that database and that set of credentials before testing. + +The testing database will also need migrating and seeding beforehand. This can be done by running `composer refresh-test-database`. + +Once done you can run `composer test` in the application root directory to run all tests. Tests can be ran in parallel by running them via `composer t`. This will use Laravel's built-in parallel testing functionality, and attempt to create and seed a database instance for each testing thread. If required these parallel testing instances can be reset, before testing again, by running `composer t-reset`. + +## Code Standards + +PHP code standards are managed by [using PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer). +Static analysis is in place using [PHPStan](https://phpstan.org/) & [Larastan](https://github.com/nunomaduro/larastan). +The below commands can be used to utilise these tools: + +```bash +# Run code linting using PHP_CodeSniffer +composer lint + +# As above, but show rule names in output +composer lint -- -s + +# Auto-fix formatting & lint issues via PHP_CodeSniffer phpcbf +composer format + +# Run static analysis via larastan/phpstan +composer check-static +``` + +If submitting a PR, formatting as per our project standards would help for clarity but don't worry too much about using/understanding these tools as we can always address issues at a later stage when they're picked up by our automated tools. + +## 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. **Copy `.env.example` to `.env`**, change `APP_KEY` to a random 32 char string and set `APP_ENV` to `local`. +2. Make sure **port 8080 is unused** *or else* change `DEV_PORT` to a free port on your host. +3. **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. +4. **Run `docker-compose up`** and wait until the image is built and all database migrations have been done. +5. You can now login with `admin@admin.com` 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: + +```bash +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. + +### Running tests + +After starting the general development Docker, migrate & seed the testing database: + + ```bash +# This only needs to be done once +docker-compose run app php artisan migrate --database=mysql_testing +docker-compose run app php artisan db:seed --class=DummyContentSeeder --database=mysql_testing +``` + +Once the database has been migrated & seeded, you can run the tests like so: + + ```bash +docker-compose run app php vendor/bin/phpunit +``` + +### Debugging + +The docker-compose setup ships with Xdebug, which you can listen to on port 9090. +NB : For some editors like Visual Studio Code, you might need to map your workspace folder to the /app folder within the docker container for this to work. diff --git a/dev/docs/release-process.md b/dev/docs/release-process.md new file mode 100644 index 000000000..758d6db4c --- /dev/null +++ b/dev/docs/release-process.md @@ -0,0 +1,24 @@ +# Release Versioning & Process + +### BookStack Version Number Scheme + +BookStack releases are each assigned a date-based version number in the format `v.[.]`. For example: + +- `v20.12` - New feature released launched during December 2020. +- `v21.06.2` - Second patch release upon the June 2021 feature release. + +Patch releases are generally fairly minor, primarily intended for fixes and therefore are fairly unlikely to cause breakages upon update. +Feature releases are generally larger, bringing new features in addition to fixes and enhancements. These releases have a greater 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/). + +### Release Planning Process + +Each BookStack release will have a [milestone](https://github.com/BookStackApp/BookStack/milestones) created with issues & pull requests assigned to it to define what will be in that release. Milestones are built up then worked through until complete at which point, after some testing and documentation updates, the release will be deployed. + +### Release Announcements + +Feature releases, and some patch releases, 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 blog posts (once per week maximum) [at this link](https://updates.bookstackapp.com/signup/bookstack-news-and-updates). + +### Release Technical Process + +Deploying a release, at a high level, simply involves merging the development branch into the release branch before then building & committing any release-only assets. +A helper script [can be found in our](https://github.com/BookStackApp/devops/blob/main/meta-scripts/bookstack-release-steps) devops repo which provides the steps and commands for deploying a new release. \ No newline at end of file diff --git a/readme.md b/readme.md index 469ec88fd..b8bd17232 100644 --- a/readme.md +++ b/readme.md @@ -59,126 +59,20 @@ Note: Listed services are not tested, vetted nor supported by the official BookS ## 🛣️ 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. +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 our [Release Process](dev/docs/release-process.md) documentation. - **Platform REST API** - *(Most actions implemented, maturing)* - *A REST API covering, at minimum, control of core content models (Books, Chapters, Pages) for automation and platform extension.* -- **Editor Alignment & Review** - *(Done)* - - *Review the page editors with the goal of achieving increased interoperability & feature parity while also considering collaborative editing potential.* - **Permission System Review** - *(In Progress)* - *Improvement in how permissions are applied and a review of the efficiency of the permission & roles system.* -- **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 - -BookStack releases are each assigned a date-based version number in the format `v.[.]`. For example: - -- `v20.12` - New feature released launched during December 2020. -- `v21.06.2` - Second patch release upon the June 2021 feature release. - -Patch releases are generally fairly minor, primarily intended for fixes and therefore are fairly unlikely to cause breakages upon update. -Feature releases are generally larger, bringing new features in addition to fixes and enhancements. These releases have a greater 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/). - -Each BookStack release will have a [milestone](https://github.com/BookStackApp/BookStack/milestones) created with issues & pull requests assigned to it to define what will be in that release. Milestones are built up then worked through until complete at which point, after some testing and documentation updates, the release will be deployed. - -Feature releases, and some patch releases, 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 blog posts (once per week maximum) [at this link](https://updates.bookstackapp.com/signup/bookstack-news-and-updates). ## 🛠️ Development & Testing -All development on BookStack is currently done on the `development` branch. When it's time for a release the `development` branch is merged into release with built & minified CSS & JS then tagged at its version. Here are the current development requirements: +Please see our [development docs](dev/docs/development.md) for full details regarding work on the BookStack source code. -* [Node.js](https://nodejs.org/en/) v14.0+ +If you're just looking to customize or extend your own BookStack instance, take a look at our [Hacking BookStack documentation page](https://www.bookstackapp.com/docs/admin/hacking-bookstack/) for details on various options to achieve this without altering the BookStack source code. -This project uses SASS for CSS development and this is built, along with the JavaScript, using a range of npm scripts. The below npm commands can be used to install the dependencies & run the build tasks: - -``` bash -# Install NPM Dependencies -npm install - -# Build assets for development -npm run build - -# Build and minify assets for production -npm run production - -# Build for dev (With sourcemaps) and watch for changes -npm run dev -``` - -BookStack has many integration tests that use Laravel's built-in testing capabilities which makes use of PHPUnit. There is a `mysql_testing` database defined within the app config which is what is used by PHPUnit. This database is set with the database name, user name and password all defined as `bookstack-test`. You will have to create that database and that set of credentials before testing. - -The testing database will also need migrating and seeding beforehand. This can be done by running `composer refresh-test-database`. - -Once done you can run `composer test` in the application root directory to run all tests. Tests can be ran in parallel by running them via `composer t`. This will use Laravel's built-in parallel testing functionality, and attempt to create and seed a database instance for each testing thread. If required these parallel testing instances can be reset, before testing again, by running `composer t-reset`. - -### 📜 Code Standards - -PHP code standards are managed by [using PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer). -Static analysis is in place using [PHPStan](https://phpstan.org/) & [Larastan](https://github.com/nunomaduro/larastan). -The below commands can be used to utilise these tools: - -```bash -# Run code linting using PHP_CodeSniffer -composer lint - -# As above, but show rule names in output -composer lint -- -s - -# Auto-fix formatting & lint issues via PHP_CodeSniffer phpcbf -composer format - -# Run static analysis via larastan/phpstan -composer check-static -``` - -If submitting a PR, formatting as per our project standards would help for clarity but don't worry too much about using/understanding these tools as we can always address issues at a later stage when they're picked up by our automated tools. - -### 🐋 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. **Copy `.env.example` to `.env`**, change `APP_KEY` to a random 32 char string and set `APP_ENV` to `local`. -2. Make sure **port 8080 is unused** *or else* change `DEV_PORT` to a free port on your host. -3. **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. -4. **Run `docker-compose up`** and wait until the image is built and all database migrations have been done. -5. You can now login with `admin@admin.com` 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: - -```bash -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. - -#### Running tests - -After starting the general development Docker, migrate & seed the testing database: - - ```bash -# This only needs to be done once -docker-compose run app php artisan migrate --database=mysql_testing -docker-compose run app php artisan db:seed --class=DummyContentSeeder --database=mysql_testing -``` - -Once the database has been migrated & seeded, you can run the tests like so: - - ```bash -docker-compose run app php vendor/bin/phpunit -``` - -#### Debugging - -The docker-compose setup ships with Xdebug, which you can listen to on port 9090. -NB : For some editors like Visual Studio Code, you might need to map your workspace folder to the /app folder within the docker container for this to work. +Details about BookStack's versioning scheme and the general release process [can be found here](dev/docs/release-process.md). ## 🌎 Translations @@ -212,7 +106,7 @@ We want BookStack to remain accessible to as many people as possible. We aim for ## 🖥️ Website, Docs & Blog -The website which contains the project docs & Blog can be found in the [BookStackApp/website](https://github.com/BookStackApp/website) repo. +The website which contains the project docs & blog can be found in the [BookStackApp/website](https://github.com/BookStackApp/website) repo. ## ⚖️ License From 1ac1cf0c786b783a17bcc60828e42ca13a683c11 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 28 Sep 2022 11:10:06 +0100 Subject: [PATCH 14/55] Applied permissions to revision action visibility Related to #3723 --- .../pages/parts/revision-table-row.blade.php | 74 +++++----- tests/Entity/PageRevisionTest.php | 131 ++++++++++-------- 2 files changed, 117 insertions(+), 88 deletions(-) diff --git a/resources/views/pages/parts/revision-table-row.blade.php b/resources/views/pages/parts/revision-table-row.blade.php index bd891d6c4..24301adc3 100644 --- a/resources/views/pages/parts/revision-table-row.blade.php +++ b/resources/views/pages/parts/revision-table-row.blade.php @@ -30,40 +30,46 @@ {{ trans('entities.pages_revisions_current') }} @else {{ trans('entities.pages_revisions_preview') }} -  |  - -  |  - + + @if(userCan('page-update', $revision->page)) +  |  + + @endif + + @if(userCan('page-delete', $revision->page)) +  |  + + @endif @endif \ No newline at end of file diff --git a/tests/Entity/PageRevisionTest.php b/tests/Entity/PageRevisionTest.php index 5ddad8441..05c86c97d 100644 --- a/tests/Entity/PageRevisionTest.php +++ b/tests/Entity/PageRevisionTest.php @@ -4,7 +4,6 @@ namespace Tests\Entity; use BookStack\Actions\ActivityType; use BookStack\Entities\Models\Page; -use BookStack\Entities\Repos\PageRepo; use Tests\TestCase; class PageRevisionTest extends TestCase @@ -23,30 +22,26 @@ class PageRevisionTest extends TestCase public function test_page_revision_views_viewable() { $this->asEditor(); - - $pageRepo = app(PageRepo::class); $page = Page::first(); - $pageRepo->update($page, ['name' => 'updated page', 'html' => '

new content

', 'summary' => 'page revision testing']); + $this->createRevisions($page, 1, ['name' => 'updated page', 'html' => '

new content

']); $pageRevision = $page->revisions->last(); - $revisionView = $this->get($page->getUrl() . '/revisions/' . $pageRevision->id); - $revisionView->assertStatus(200); - $revisionView->assertSee('new content'); + $resp = $this->get($page->getUrl() . '/revisions/' . $pageRevision->id); + $resp->assertStatus(200); + $resp->assertSee('new content'); - $revisionView = $this->get($page->getUrl() . '/revisions/' . $pageRevision->id . '/changes'); - $revisionView->assertStatus(200); - $revisionView->assertSee('new content'); + $resp = $this->get($page->getUrl() . '/revisions/' . $pageRevision->id . '/changes'); + $resp->assertStatus(200); + $resp->assertSee('new content'); } public function test_page_revision_preview_shows_content_of_revision() { $this->asEditor(); - - $pageRepo = app(PageRepo::class); $page = Page::first(); - $pageRepo->update($page, ['name' => 'updated page', 'html' => '

new revision content

', 'summary' => 'page revision testing']); + $this->createRevisions($page, 1, ['name' => 'updated page', 'html' => '

new revision content

']); $pageRevision = $page->revisions->last(); - $pageRepo->update($page, ['name' => 'updated page', 'html' => '

Updated content

', 'summary' => 'page revision testing 2']); + $this->createRevisions($page, 1, ['name' => 'updated page', 'html' => '

Updated content

']); $revisionView = $this->get($page->getUrl() . '/revisions/' . $pageRevision->id); $revisionView->assertStatus(200); @@ -56,11 +51,9 @@ class PageRevisionTest extends TestCase public function test_page_revision_restore_updates_content() { $this->asEditor(); - - $pageRepo = app(PageRepo::class); $page = Page::first(); - $pageRepo->update($page, ['name' => 'updated page abc123', 'html' => '

new contente def456

', 'summary' => 'initial page revision testing']); - $pageRepo->update($page, ['name' => 'updated page again', 'html' => '

new content

', 'summary' => 'page revision testing']); + $this->createRevisions($page, 1, ['name' => 'updated page abc123', 'html' => '

new contente def456

']); + $this->createRevisions($page, 1, ['name' => 'updated page again', 'html' => '

new content

']); $page = Page::find($page->id); $pageView = $this->get($page->getUrl()); @@ -82,11 +75,9 @@ class PageRevisionTest extends TestCase public function test_page_revision_restore_with_markdown_retains_markdown_content() { $this->asEditor(); - - $pageRepo = app(PageRepo::class); $page = Page::first(); - $pageRepo->update($page, ['name' => 'updated page abc123', 'markdown' => '## New Content def456', 'summary' => 'initial page revision testing']); - $pageRepo->update($page, ['name' => 'updated page again', 'markdown' => '## New Content Updated', 'summary' => 'page revision testing']); + $this->createRevisions($page, 1, ['name' => 'updated page abc123', 'markdown' => '## New Content def456']); + $this->createRevisions($page, 1, ['name' => 'updated page again', 'markdown' => '## New Content Updated']); $page = Page::find($page->id); $pageView = $this->get($page->getUrl()); @@ -112,11 +103,9 @@ class PageRevisionTest extends TestCase public function test_page_revision_restore_sets_new_revision_with_summary() { $this->asEditor(); - - $pageRepo = app(PageRepo::class); $page = Page::first(); - $pageRepo->update($page, ['name' => 'updated page abc123', 'html' => '

new contente def456

', 'summary' => 'My first update']); - $pageRepo->update($page, ['name' => 'updated page again', 'html' => '

new content

', 'summary' => '']); + $this->createRevisions($page, 1, ['name' => 'updated page abc123', 'html' => '

new contente def456

', 'summary' => 'My first update']); + $this->createRevisions($page, 1, ['html' => '

new content

']); $page->refresh(); $revToRestore = $page->revisions()->where('name', 'like', '%abc123')->first(); @@ -138,8 +127,7 @@ class PageRevisionTest extends TestCase { $page = Page::first(); $startCount = $page->revision_count; - $resp = $this->asEditor()->put($page->getUrl(), ['name' => 'Updated page', 'html' => 'new page html', 'summary' => 'Update a']); - $resp->assertStatus(302); + $this->createRevisions($page, 1); $this->assertTrue(Page::find($page->id)->revision_count === $startCount + 1); } @@ -147,12 +135,8 @@ class PageRevisionTest extends TestCase public function test_revision_count_shown_in_page_meta() { $page = Page::first(); - $this->asEditor()->put($page->getUrl(), ['name' => 'Updated page', 'html' => 'new page html', 'summary' => 'Update a']); + $this->createRevisions($page, 2); - $page = Page::find($page->id); - $this->asEditor()->put($page->getUrl(), ['name' => 'Updated page', 'html' => 'new page html', 'summary' => 'Update a']); - - $page = Page::find($page->id); $pageView = $this->get($page->getUrl()); $pageView->assertSee('Revision #' . $page->revision_count); } @@ -161,12 +145,7 @@ class PageRevisionTest extends TestCase { /** @var Page $page */ $page = Page::query()->first(); - $this->asEditor()->put($page->getUrl(), ['name' => 'Updated page', 'html' => 'new page html', 'summary' => 'Update a']); - - $page->refresh(); - $this->asEditor()->put($page->getUrl(), ['name' => 'Updated page', 'html' => 'new page html', 'summary' => 'Update a']); - - $page->refresh(); + $this->createRevisions($page, 2); $beforeRevisionCount = $page->revisions->count(); // Delete the first revision @@ -196,12 +175,7 @@ class PageRevisionTest extends TestCase { config()->set('app.revision_limit', 2); $page = Page::first(); - $this->asEditor()->put($page->getUrl(), ['name' => 'Updated page', 'html' => 'new page html', 'summary' => 'Update a']); - $page = Page::find($page->id); - $this->asEditor()->put($page->getUrl(), ['name' => 'Updated page', 'html' => 'new page html', 'summary' => 'Update a']); - for ($i = 0; $i < 10; $i++) { - $this->asEditor()->put($page->getUrl(), ['name' => 'Updated page', 'html' => 'new page html', 'summary' => 'Update a']); - } + $this->createRevisions($page, 12); $revisionCount = $page->revisions()->count(); $this->assertEquals(2, $revisionCount); @@ -211,12 +185,7 @@ class PageRevisionTest extends TestCase { config()->set('app.revision_limit', false); $page = Page::first(); - $this->asEditor()->put($page->getUrl(), ['name' => 'Updated page', 'html' => 'new page html', 'summary' => 'Update a']); - $page = Page::find($page->id); - $this->asEditor()->put($page->getUrl(), ['name' => 'Updated page', 'html' => 'new page html', 'summary' => 'Update a']); - for ($i = 0; $i < 10; $i++) { - $this->asEditor()->put($page->getUrl(), ['name' => 'Updated page', 'html' => 'new page html', 'summary' => 'Update a']); - } + $this->createRevisions($page, 12); $revisionCount = $page->revisions()->count(); $this->assertEquals(12, $revisionCount); @@ -226,14 +195,68 @@ class PageRevisionTest extends TestCase { /** @var Page $page */ $page = Page::first(); - $this->asAdmin()->put($page->getUrl(), ['name' => 'Updated page', 'html' => 'new page html']); + $this->createRevisions($page, 1, ['html' => 'new page html']); - $resp = $this->get($page->refresh()->getUrl('/revisions')); + $resp = $this->asAdmin()->get($page->refresh()->getUrl('/revisions')); $this->withHtml($resp)->assertElementContains('td', '(WYSIWYG)'); $this->withHtml($resp)->assertElementNotContains('td', '(Markdown)'); - $this->asAdmin()->put($page->getUrl(), ['name' => 'Updated page', 'markdown' => '# Some markdown content']); + $this->createRevisions($page, 1, ['markdown' => '# Some markdown content']); $resp = $this->get($page->refresh()->getUrl('/revisions')); $this->withHtml($resp)->assertElementContains('td', '(Markdown)'); } + + public function test_revision_restore_action_only_visible_with_permission() + { + /** @var Page $page */ + $page = Page::query()->first(); + $this->createRevisions($page, 2); + + $viewer = $this->getViewer(); + $this->actingAs($viewer); + $respHtml = $this->withHtml($this->get($page->getUrl('/revisions'))); + $respHtml->assertElementNotContains('.actions a', 'Restore'); + $respHtml->assertElementNotExists('form[action$="/restore"]'); + + $this->giveUserPermissions($viewer, ['page-update-all']); + + $respHtml = $this->withHtml($this->get($page->getUrl('/revisions'))); + $respHtml->assertElementContains('.actions a', 'Restore'); + $respHtml->assertElementExists('form[action$="/restore"]'); + } + + public function test_revision_delete_action_only_visible_with_permission() + { + /** @var Page $page */ + $page = Page::query()->first(); + $this->createRevisions($page, 2); + + $viewer = $this->getViewer(); + $this->actingAs($viewer); + $respHtml = $this->withHtml($this->get($page->getUrl('/revisions'))); + $respHtml->assertElementNotContains('.actions a', 'Delete'); + $respHtml->assertElementNotExists('form[action$="/delete"]'); + + $this->giveUserPermissions($viewer, ['page-delete-all']); + + $respHtml = $this->withHtml($this->get($page->getUrl('/revisions'))); + $respHtml->assertElementContains('.actions a', 'Delete'); + $respHtml->assertElementExists('form[action$="/delete"]'); + } + + protected function createRevisions(Page $page, int $times, array $attrs = []) + { + $user = user(); + + for ($i = 0; $i < $times; $i++) { + $data = ['name' => 'Page update' . $i, 'summary' => 'Update entry' . $i]; + if (!isset($attrs['markdown'])) { + $data['html'] = '

My update page

'; + } + $this->asAdmin()->put($page->getUrl(), array_merge($data, $attrs)); + $page->refresh(); + } + + $this->actingAs($user); + } } From 8f3430d3867ca8880c4f259b2c58647eb2b2c5ea Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 28 Sep 2022 13:50:40 +0100 Subject: [PATCH 15/55] Improved tag suggestion handling - Aligned prefix-type filtering with back-end. - Increased suggestion search cut-off from 3 to 4. - Increased amount of suggestions shown. - Ordered suggestions to be name asc, as you'd expect on search. - Updated front-end filtering to use full search query, instead of truncated version, for further front-end filtering capability. Related to #3720 --- app/Actions/TagRepo.php | 10 +++++----- app/Http/Controllers/TagController.php | 11 ++++------- resources/js/components/auto-suggest.js | 15 ++++++++------- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/app/Actions/TagRepo.php b/app/Actions/TagRepo.php index 172d8ec6e..2618ed2e9 100644 --- a/app/Actions/TagRepo.php +++ b/app/Actions/TagRepo.php @@ -57,21 +57,21 @@ class TagRepo * Get tag name suggestions from scanning existing tag names. * If no search term is given the 50 most popular tag names are provided. */ - public function getNameSuggestions(?string $searchTerm): Collection + public function getNameSuggestions(string $searchTerm): Collection { $query = Tag::query() ->select('*', DB::raw('count(*) as count')) ->groupBy('name'); if ($searchTerm) { - $query = $query->where('name', 'LIKE', $searchTerm . '%')->orderBy('name', 'desc'); + $query = $query->where('name', 'LIKE', $searchTerm . '%')->orderBy('name', 'asc'); } else { $query = $query->orderBy('count', 'desc')->take(50); } $query = $this->permissions->restrictEntityRelationQuery($query, 'tags', 'entity_id', 'entity_type'); - return $query->get(['name'])->pluck('name'); + return $query->pluck('name'); } /** @@ -79,7 +79,7 @@ class TagRepo * If no search is given the 50 most popular values are provided. * Passing a tagName will only find values for a tags with a particular name. */ - public function getValueSuggestions(?string $searchTerm, ?string $tagName): Collection + public function getValueSuggestions(string $searchTerm, string $tagName): Collection { $query = Tag::query() ->select('*', DB::raw('count(*) as count')) @@ -97,7 +97,7 @@ class TagRepo $query = $this->permissions->restrictEntityRelationQuery($query, 'tags', 'entity_id', 'entity_type'); - return $query->get(['value'])->pluck('value'); + return $query->pluck('value'); } /** diff --git a/app/Http/Controllers/TagController.php b/app/Http/Controllers/TagController.php index e59580b60..056cc9902 100644 --- a/app/Http/Controllers/TagController.php +++ b/app/Http/Controllers/TagController.php @@ -7,11 +7,8 @@ use Illuminate\Http\Request; class TagController extends Controller { - protected $tagRepo; + protected TagRepo $tagRepo; - /** - * TagController constructor. - */ public function __construct(TagRepo $tagRepo) { $this->tagRepo = $tagRepo; @@ -46,7 +43,7 @@ class TagController extends Controller */ public function getNameSuggestions(Request $request) { - $searchTerm = $request->get('search', null); + $searchTerm = $request->get('search', ''); $suggestions = $this->tagRepo->getNameSuggestions($searchTerm); return response()->json($suggestions); @@ -57,8 +54,8 @@ class TagController extends Controller */ public function getValueSuggestions(Request $request) { - $searchTerm = $request->get('search', null); - $tagName = $request->get('name', null); + $searchTerm = $request->get('search', ''); + $tagName = $request->get('name', ''); $suggestions = $this->tagRepo->getValueSuggestions($searchTerm, $tagName); return response()->json($suggestions); diff --git a/resources/js/components/auto-suggest.js b/resources/js/components/auto-suggest.js index d1c15c00a..80857cbe5 100644 --- a/resources/js/components/auto-suggest.js +++ b/resources/js/components/auto-suggest.js @@ -88,14 +88,12 @@ class AutoSuggest { } const nameFilter = this.getNameFilterIfNeeded(); - const search = this.input.value.slice(0, 3).toLowerCase(); + const search = this.input.value.toLowerCase(); const suggestions = await this.loadSuggestions(search, nameFilter); - let toShow = suggestions.slice(0, 6); - if (search.length > 0) { - toShow = suggestions.filter(val => { - return val.toLowerCase().includes(search); - }).slice(0, 6); - } + + const toShow = suggestions.filter(val => { + return search === '' || val.toLowerCase().startsWith(search); + }).slice(0, 10); this.displaySuggestions(toShow); } @@ -111,6 +109,9 @@ class AutoSuggest { * @returns {Promise} */ async loadSuggestions(search, nameFilter = null) { + // Truncate search to prevent over numerous lookups + search = search.slice(0, 4); + const params = {search, name: nameFilter}; const cacheKey = `${this.url}:${JSON.stringify(params)}`; From 60171b3522220e874436f52cc8936f6879e7e60f Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 28 Sep 2022 14:14:51 +0100 Subject: [PATCH 16/55] Updated book copy to copy shelf relations Where permission to edit the shelf is allowed. For #3699 --- app/Entities/Models/Book.php | 1 + app/Entities/Tools/Cloner.php | 11 +++++++++++ tests/Entity/BookTest.php | 26 +++++++++++++++++++++++++- 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/app/Entities/Models/Book.php b/app/Entities/Models/Book.php index 8217d2cab..bf42f2008 100644 --- a/app/Entities/Models/Book.php +++ b/app/Entities/Models/Book.php @@ -19,6 +19,7 @@ use Illuminate\Support\Collection; * @property \Illuminate\Database\Eloquent\Collection $chapters * @property \Illuminate\Database\Eloquent\Collection $pages * @property \Illuminate\Database\Eloquent\Collection $directPages + * @property \Illuminate\Database\Eloquent\Collection $shelves */ class Book extends Entity implements HasCoverImage { diff --git a/app/Entities/Tools/Cloner.php b/app/Entities/Tools/Cloner.php index 92b62a754..86f392e61 100644 --- a/app/Entities/Tools/Cloner.php +++ b/app/Entities/Tools/Cloner.php @@ -4,6 +4,7 @@ namespace BookStack\Entities\Tools; use BookStack\Actions\Tag; use BookStack\Entities\Models\Book; +use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\Page; @@ -71,8 +72,10 @@ class Cloner $bookDetails = $this->entityToInputData($original); $bookDetails['name'] = $newName; + // Clone book $copyBook = $this->bookRepo->create($bookDetails); + // Clone contents $directChildren = $original->getDirectChildren(); foreach ($directChildren as $child) { if ($child instanceof Chapter && userCan('chapter-create', $copyBook)) { @@ -84,6 +87,14 @@ class Cloner } } + // Clone bookshelf relationships + /** @var Bookshelf $shelf */ + foreach ($original->shelves as $shelf) { + if (userCan('bookshelf-update', $shelf)) { + $shelf->appendBook($copyBook); + } + } + return $copyBook; } diff --git a/tests/Entity/BookTest.php b/tests/Entity/BookTest.php index 2e6f8e9de..2f04fcf25 100644 --- a/tests/Entity/BookTest.php +++ b/tests/Entity/BookTest.php @@ -4,6 +4,7 @@ namespace Tests\Entity; use BookStack\Entities\Models\Book; use BookStack\Entities\Models\BookChild; +use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Repos\BookRepo; use Tests\TestCase; use Tests\Uploads\UsesImages; @@ -344,11 +345,34 @@ class BookTest extends TestCase $bookRepo->updateCoverImage($book, $coverImageFile); $this->asEditor()->post($book->getUrl('/copy'), ['name' => 'My copy book']); - /** @var Book $copy */ $copy = Book::query()->where('name', '=', 'My copy book')->first(); $this->assertNotNull($copy->cover); $this->assertNotEquals($book->cover->id, $copy->cover->id); } + + public function test_copy_adds_book_to_shelves_if_edit_permissions_allows() + { + /** @var Bookshelf $shelfA */ + /** @var Bookshelf $shelfB */ + [$shelfA, $shelfB] = Bookshelf::query()->take(2)->get(); + /** @var Book $book */ + $book = Book::query()->first(); + + $shelfA->appendBook($book); + $shelfB->appendBook($book); + + $viewer = $this->getViewer(); + $this->giveUserPermissions($viewer, ['book-update-all', 'book-create-all', 'bookshelf-update-all']); + $this->setEntityRestrictions($shelfB); + + + $this->asEditor()->post($book->getUrl('/copy'), ['name' => 'My copy book']); + /** @var Book $copy */ + $copy = Book::query()->where('name', '=', 'My copy book')->first(); + + $this->assertTrue($copy->shelves()->where('id', '=', $shelfA->id)->exists()); + $this->assertFalse($copy->shelves()->where('id', '=', $shelfB->id)->exists()); + } } From f79b7bc7994cbaeca6764ef2602b569cc9b07a52 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 28 Sep 2022 20:15:48 +0100 Subject: [PATCH 17/55] Added api format advisory regarding PUT/DELETE form data --- .../views/api-docs/parts/getting-started.blade.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/resources/views/api-docs/parts/getting-started.blade.php b/resources/views/api-docs/parts/getting-started.blade.php index 76da73e45..7358b5cd7 100644 --- a/resources/views/api-docs/parts/getting-started.blade.php +++ b/resources/views/api-docs/parts/getting-started.blade.php @@ -53,10 +53,19 @@
  • application/json
  • -
  • application/x-www-form-urlencoded
  • -
  • multipart/form-data
  • +
  • application/x-www-form-urlencoded*
  • +
  • multipart/form-data*
+

+ + * Form requests currently only work for POST requests due to how PHP handles request data. + If you need to use these formats for PUT or DELETE requests you can work around this limitation by + using a POST request and providing a "_method" parameter with the value equal to + PUT or DELETE. + +

+

Regardless of format chosen, ensure you set a Content-Type header on requests so that the system can correctly parse your request data. The API is primarily designed to be interfaced using JSON, since responses are always in JSON format, hence examples in this documentation will be shown as JSON. From ccbc68b5600790546e073546a31b8123d5693411 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 28 Sep 2022 20:48:29 +0100 Subject: [PATCH 18/55] Updated shelf book management to allow scroll on mobile Updates book drag handling to be limited to the handle so scrolling can be done on the items themselves. Increased handling area and improved styling to support --- resources/js/components/shelf-sort.js | 1 + resources/sass/styles.scss | 52 +++++++++++++++++---------- 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/resources/js/components/shelf-sort.js b/resources/js/components/shelf-sort.js index 07526716a..30eda5a21 100644 --- a/resources/js/components/shelf-sort.js +++ b/resources/js/components/shelf-sort.js @@ -19,6 +19,7 @@ class ShelfSort { new Sortable(scrollBox, { group: 'shelf-books', ghostClass: 'primary-background-light', + handle: '.handle', animation: 150, onSort: this.onChange.bind(this), }); diff --git a/resources/sass/styles.scss b/resources/sass/styles.scss index 65eee866d..ab97466a5 100644 --- a/resources/sass/styles.scss +++ b/resources/sass/styles.scss @@ -246,26 +246,40 @@ $btt-size: 40px; border-radius: 3px; min-height: 20px; @include lightDark(background-color, #EEE, #000); - .scroll-box-item { +} +.scroll-box-item { + border-bottom: 1px solid; + border-top: 1px solid; + @include lightDark(border-color, #DDD, #000); + margin-top: -1px; + @include lightDark(background-color, #FFF, #222); + display: flex; + padding: 1px; + &:last-child { + border-bottom: 0; + } + &:hover { + cursor: pointer; + @include lightDark(background-color, #f8f8f8, #333); + } + .handle { + color: #AAA; + cursor: grab; + } + .handle svg { + margin: 0; + } + > * { padding: $-xs $-m; - border-bottom: 1px solid; - border-top: 1px solid; - @include lightDark(border-color, #DDD, #000); - margin-top: -1px; - @include lightDark(background-color, #FFF, #222); - display: flex; - gap: $-xs; - &:last-child { - border-bottom: 0; - } - &:hover { - cursor: pointer; - @include lightDark(background-color, #f8f8f8, #333); - } - .handle { - color: #AAA; - cursor: grab; - } + } + .handle + * { + padding-left: 0; + } + &:hover .handle { + @include lightDark(color, #444, #FFF); + } + a:hover { + text-decoration: none; } } From 0e94fd44a8925c695441768eee0ecbce7bb239bc Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 29 Sep 2022 15:05:57 +0100 Subject: [PATCH 19/55] Added contents to book-show endpoint Created a generic list formatting helper class for this, to align with logic used on the search results endpoint and for easier future re-use in a standardised way. Also updated some class property types. Added test to cover new books-contents results. Related to #3734 --- app/Api/ApiEntityListFormatter.php | 107 ++++++++++++++++++ app/Entities/Tools/BookContents.php | 11 +- .../Controllers/Api/BookApiController.php | 20 +++- .../Api/BookshelfApiController.php | 3 - .../Controllers/Api/SearchApiController.php | 30 ++--- dev/api/responses/books-read.json | 42 ++++++- tests/Api/BooksApiTest.php | 32 ++++++ 7 files changed, 212 insertions(+), 33 deletions(-) create mode 100644 app/Api/ApiEntityListFormatter.php diff --git a/app/Api/ApiEntityListFormatter.php b/app/Api/ApiEntityListFormatter.php new file mode 100644 index 000000000..c170ecf0c --- /dev/null +++ b/app/Api/ApiEntityListFormatter.php @@ -0,0 +1,107 @@ + + */ + protected $fields = [ + 'id', 'name', 'slug', 'book_id', 'chapter_id', + 'draft', 'template', 'created_at', 'updated_at', + ]; + + public function __construct(array $list) + { + $this->list = $list; + + // Default dynamic fields + $this->withField('url', fn(Entity $entity) => $entity->getUrl()); + } + + /** + * Add a field to be used in the formatter, with the property using the given + * name and value being the return type of the given callback. + */ + public function withField(string $property, callable $callback): self + { + $this->fields[$property] = $callback; + return $this; + } + + /** + * Show the 'type' property in the response reflecting the entity type. + * EG: page, chapter, bookshelf, book + * To be included in results with non-pre-determined types. + */ + public function withType(): self + { + $this->withField('type', fn(Entity $entity) => $entity->getType()); + return $this; + } + + /** + * Include tags in the formatted data. + */ + public function withTags(): self + { + $this->withField('tags', fn(Entity $entity) => $entity->tags); + return $this; + } + + /** + * Format the data and return an array of formatted content. + * @return array[] + */ + public function format(): array + { + $results = []; + + foreach ($this->list as $item) { + $results[] = $this->formatSingle($item); + } + + return $results; + } + + /** + * Format a single entity item to a plain array. + */ + protected function formatSingle(Entity $entity): array + { + $result = []; + $values = (clone $entity)->toArray(); + + foreach ($this->fields as $field => $callback) { + if (is_string($callback)) { + $field = $callback; + if (!isset($values[$field])) { + continue; + } + $value = $values[$field]; + } else { + $value = $callback($entity); + if (is_null($value)) { + continue; + } + } + + $result[$field] = $value; + } + + return $result; + } +} diff --git a/app/Entities/Tools/BookContents.php b/app/Entities/Tools/BookContents.php index 6f11e8cbe..0ad424de2 100644 --- a/app/Entities/Tools/BookContents.php +++ b/app/Entities/Tools/BookContents.php @@ -11,22 +11,15 @@ use Illuminate\Support\Collection; class BookContents { - /** - * @var Book - */ - protected $book; + protected Book $book; - /** - * BookContents constructor. - */ public function __construct(Book $book) { $this->book = $book; } /** - * Get the current priority of the last item - * at the top-level of the book. + * Get the current priority of the last item at the top-level of the book. */ public function getLastPriority(): int { diff --git a/app/Http/Controllers/Api/BookApiController.php b/app/Http/Controllers/Api/BookApiController.php index 15565c361..d57b48a43 100644 --- a/app/Http/Controllers/Api/BookApiController.php +++ b/app/Http/Controllers/Api/BookApiController.php @@ -2,14 +2,18 @@ namespace BookStack\Http\Controllers\Api; +use BookStack\Api\ApiEntityListFormatter; use BookStack\Entities\Models\Book; +use BookStack\Entities\Models\Chapter; +use BookStack\Entities\Models\Entity; use BookStack\Entities\Repos\BookRepo; +use BookStack\Entities\Tools\BookContents; use Illuminate\Http\Request; use Illuminate\Validation\ValidationException; class BookApiController extends ApiController { - protected $bookRepo; + protected BookRepo $bookRepo; public function __construct(BookRepo $bookRepo) { @@ -47,11 +51,25 @@ class BookApiController extends ApiController /** * View the details of a single book. + * The response data will contain 'content' property listing the chapter and pages directly within, in + * the same structure as you'd see within the BookStack interface when viewing a book. Top-level + * contents will have a 'type' property to distinguish between pages & chapters. */ public function read(string $id) { $book = Book::visible()->with(['tags', 'cover', 'createdBy', 'updatedBy', 'ownedBy'])->findOrFail($id); + $contents = (new BookContents($book))->getTree(true, false)->all(); + $contentsApiData = (new ApiEntityListFormatter($contents)) + ->withType() + ->withField('pages', function (Entity $entity) { + if ($entity instanceof Chapter) { + return (new ApiEntityListFormatter($entity->pages->all()))->format(); + } + return null; + })->format(); + $book->setAttribute('contents', $contentsApiData); + return response()->json($book); } diff --git a/app/Http/Controllers/Api/BookshelfApiController.php b/app/Http/Controllers/Api/BookshelfApiController.php index 620df1638..b6b78e80e 100644 --- a/app/Http/Controllers/Api/BookshelfApiController.php +++ b/app/Http/Controllers/Api/BookshelfApiController.php @@ -13,9 +13,6 @@ class BookshelfApiController extends ApiController { protected BookshelfRepo $bookshelfRepo; - /** - * BookshelfApiController constructor. - */ public function __construct(BookshelfRepo $bookshelfRepo) { $this->bookshelfRepo = $bookshelfRepo; diff --git a/app/Http/Controllers/Api/SearchApiController.php b/app/Http/Controllers/Api/SearchApiController.php index 7ef714390..bf59ec671 100644 --- a/app/Http/Controllers/Api/SearchApiController.php +++ b/app/Http/Controllers/Api/SearchApiController.php @@ -2,6 +2,7 @@ namespace BookStack\Http\Controllers\Api; +use BookStack\Api\ApiEntityListFormatter; use BookStack\Entities\Models\Entity; use BookStack\Search\SearchOptions; use BookStack\Search\SearchResultsFormatter; @@ -10,8 +11,8 @@ use Illuminate\Http\Request; class SearchApiController extends ApiController { - protected $searchRunner; - protected $resultsFormatter; + protected SearchRunner $searchRunner; + protected SearchResultsFormatter $resultsFormatter; protected $rules = [ 'all' => [ @@ -50,24 +51,17 @@ class SearchApiController extends ApiController $results = $this->searchRunner->searchEntities($options, 'all', $page, $count); $this->resultsFormatter->format($results['results']->all(), $options); - /** @var Entity $result */ - foreach ($results['results'] as $result) { - $result->setVisible([ - 'id', 'name', 'slug', 'book_id', - 'chapter_id', 'draft', 'template', - 'created_at', 'updated_at', - 'tags', 'type', 'preview_html', 'url', - ]); - $result->setAttribute('type', $result->getType()); - $result->setAttribute('url', $result->getUrl()); - $result->setAttribute('preview_html', [ - 'name' => (string) $result->getAttribute('preview_name'), - 'content' => (string) $result->getAttribute('preview_content'), - ]); - } + $data = (new ApiEntityListFormatter($results['results']->all())) + ->withType()->withTags() + ->withField('preview_html', function (Entity $entity) { + return [ + 'name' => (string) $entity->getAttribute('preview_name'), + 'content' => (string) $entity->getAttribute('preview_content'), + ]; + })->format(); return response()->json([ - 'data' => $results['results'], + 'data' => $data, 'total' => $results['total'], ]); } diff --git a/dev/api/responses/books-read.json b/dev/api/responses/books-read.json index 7de85addc..8d584f597 100644 --- a/dev/api/responses/books-read.json +++ b/dev/api/responses/books-read.json @@ -17,6 +17,44 @@ "id": 1, "name": "Admin" }, + "contents": [ + { + "id": 50, + "name": "Bridge Structures", + "slug": "bridge-structures", + "book_id": 16, + "created_at": "2021-12-19T15:22:11.000000Z", + "updated_at": "2021-12-21T19:42:29.000000Z", + "url": "https://example.com/books/my-own-book/chapter/bridge-structures", + "type": "chapter", + "pages": [ + { + "id": 42, + "name": "Building Bridges", + "slug": "building-bridges", + "book_id": 16, + "chapter_id": 50, + "draft": false, + "template": false, + "created_at": "2021-12-19T15:22:11.000000Z", + "updated_at": "2022-09-29T13:44:15.000000Z", + "url": "https://example.com/books/my-own-book/page/building-bridges" + } + ] + }, + { + "id": 43, + "name": "Cool Animals", + "slug": "cool-animals", + "book_id": 16, + "chapter_id": 0, + "draft": false, + "template": false, + "created_at": "2021-12-19T18:22:11.000000Z", + "updated_at": "2022-07-29T13:44:15.000000Z", + "url": "https://example.com/books/my-own-book/page/cool-animals" + } + ], "tags": [ { "id": 13, @@ -28,12 +66,12 @@ "cover": { "id": 452, "name": "sjovall_m117hUWMu40.jpg", - "url": "http:\/\/bookstack.local\/uploads\/images\/cover_book\/2020-01\/sjovall_m117hUWMu40.jpg", + "url": "https://example.com/uploads/images/cover_book/2020-01/sjovall_m117hUWMu40.jpg", "created_at": "2020-01-12T14:11:51.000000Z", "updated_at": "2020-01-12T14:11:51.000000Z", "created_by": 1, "updated_by": 1, - "path": "\/uploads\/images\/cover_book\/2020-01\/sjovall_m117hUWMu40.jpg", + "path": "/uploads/images/cover_book/2020-01/sjovall_m117hUWMu40.jpg", "type": "cover_book", "uploaded_to": 16 } diff --git a/tests/Api/BooksApiTest.php b/tests/Api/BooksApiTest.php index f426cff73..017322193 100644 --- a/tests/Api/BooksApiTest.php +++ b/tests/Api/BooksApiTest.php @@ -88,6 +88,38 @@ class BooksApiTest extends TestCase ]); } + public function test_read_endpoint_includes_chapter_and_page_contents() + { + $this->actingAsApiEditor(); + /** @var Book $book */ + $book = Book::visible()->has('chapters')->has('pages')->first(); + $chapter = $book->chapters()->first(); + $chapterPage = $chapter->pages()->first(); + + $resp = $this->getJson($this->baseEndpoint . "/{$book->id}"); + + $directChildCount = $book->directPages()->count() + $book->chapters()->count(); + $resp->assertStatus(200); + $resp->assertJsonCount($directChildCount, 'contents'); + $resp->assertJson([ + 'contents' => [ + [ + 'type' => 'chapter', + 'id' => $chapter->id, + 'name' => $chapter->name, + 'slug' => $chapter->slug, + 'pages' => [ + [ + 'id' => $chapterPage->id, + 'name' => $chapterPage->name, + 'slug' => $chapterPage->slug, + ] + ] + ] + ] + ]); + } + public function test_update_endpoint() { $this->actingAsApiEditor(); From 068a8a068c5d7c7ab98a6ee95baae8d321c3c61f Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 29 Sep 2022 16:49:25 +0100 Subject: [PATCH 20/55] Extracted entity testcase methods to own class Also added some new fetch helper methods for future use. --- tests/Api/AttachmentsApiTest.php | 4 +- tests/Api/PagesApiTest.php | 2 +- tests/Api/UsersApiTest.php | 2 +- .../CopyShelfPermissionsCommandTest.php | 4 +- tests/Entity/BookShelfTest.php | 6 +- tests/Entity/BookTest.php | 10 +- tests/Entity/ChapterTest.php | 2 +- tests/Entity/EntityAccessTest.php | 4 +- tests/Entity/EntitySearchTest.php | 30 +-- tests/Entity/PageTest.php | 8 +- tests/Entity/SortTest.php | 36 ++-- tests/Entity/TagTest.php | 4 +- tests/Helpers/EntityProvider.php | 201 ++++++++++++++++++ tests/HomepageTest.php | 10 +- tests/Permissions/EntityPermissionsTest.php | 2 +- tests/Permissions/ExportPermissionsTest.php | 4 +- tests/Permissions/RolesTest.php | 40 ++-- tests/PublicActionTest.php | 2 +- tests/References/CrossLinkParserTest.php | 2 +- tests/References/ReferencesTest.php | 6 +- tests/TestCase.php | 120 +---------- tests/Uploads/ImageTest.php | 6 +- tests/User/UserProfileTest.php | 8 +- 23 files changed, 305 insertions(+), 208 deletions(-) create mode 100644 tests/Helpers/EntityProvider.php diff --git a/tests/Api/AttachmentsApiTest.php b/tests/Api/AttachmentsApiTest.php index 6077868b2..dfd57deb8 100644 --- a/tests/Api/AttachmentsApiTest.php +++ b/tests/Api/AttachmentsApiTest.php @@ -53,7 +53,7 @@ class AttachmentsApiTest extends TestCase $page->restricted = true; $page->save(); - $this->regenEntityPermissions($page); + $this->entities->regenPermissions($page); $resp = $this->getJson($this->baseEndpoint . '?count=1&sort=+id'); $resp->assertJsonMissing(['data' => [ @@ -264,7 +264,7 @@ class AttachmentsApiTest extends TestCase $page->draft = true; $page->owned_by = $editor->id; $page->save(); - $this->regenEntityPermissions($page); + $this->entities->regenPermissions($page); $attachment = $this->createAttachmentForPage($page, [ 'name' => 'my attachment', diff --git a/tests/Api/PagesApiTest.php b/tests/Api/PagesApiTest.php index 539a7da4e..20c6977dd 100644 --- a/tests/Api/PagesApiTest.php +++ b/tests/Api/PagesApiTest.php @@ -210,7 +210,7 @@ class PagesApiTest extends TestCase $this->actingAsApiEditor(); $page = Page::visible()->first(); $chapter = Chapter::visible()->where('book_id', '!=', $page->book_id)->first(); - $this->setEntityRestrictions($chapter, ['view'], [$this->getEditor()->roles()->first()]); + $this->entities->setPermissions($chapter, ['view'], [$this->getEditor()->roles()->first()]); $details = [ 'name' => 'My updated API page', 'chapter_id' => $chapter->id, diff --git a/tests/Api/UsersApiTest.php b/tests/Api/UsersApiTest.php index ddbdac0f8..739981f24 100644 --- a/tests/Api/UsersApiTest.php +++ b/tests/Api/UsersApiTest.php @@ -239,7 +239,7 @@ class UsersApiTest extends TestCase $user = User::query()->where('id', '!=', $this->getAdmin()->id) ->whereNull('system_name') ->first(); - $entityChain = $this->createEntityChainBelongingToUser($user); + $entityChain = $this->entities->createChainBelongingToUser($user); /** @var User $newOwner */ $newOwner = User::query()->where('id', '!=', $user->id)->first(); diff --git a/tests/Commands/CopyShelfPermissionsCommandTest.php b/tests/Commands/CopyShelfPermissionsCommandTest.php index 5a60b8d55..dd39317ae 100644 --- a/tests/Commands/CopyShelfPermissionsCommandTest.php +++ b/tests/Commands/CopyShelfPermissionsCommandTest.php @@ -22,7 +22,7 @@ class CopyShelfPermissionsCommandTest extends TestCase $this->assertFalse(boolval($child->restricted), 'Child book should not be restricted by default'); $this->assertTrue($child->permissions()->count() === 0, 'Child book should have no permissions by default'); - $this->setEntityRestrictions($shelf, ['view', 'update'], [$editorRole]); + $this->entities->setPermissions($shelf, ['view', 'update'], [$editorRole]); $this->artisan('bookstack:copy-shelf-permissions', [ '--slug' => $shelf->slug, ]); @@ -43,7 +43,7 @@ class CopyShelfPermissionsCommandTest extends TestCase $this->assertFalse(boolval($child->restricted), 'Child book should not be restricted by default'); $this->assertTrue($child->permissions()->count() === 0, 'Child book should have no permissions by default'); - $this->setEntityRestrictions($shelf, ['view', 'update'], [$editorRole]); + $this->entities->setPermissions($shelf, ['view', 'update'], [$editorRole]); $this->artisan('bookstack:copy-shelf-permissions --all') ->expectsQuestion('Permission settings for all shelves will be cascaded. Books assigned to multiple shelves will receive only the permissions of it\'s last processed shelf. Are you sure you want to proceed?', 'y'); $child = $shelf->books()->first(); diff --git a/tests/Entity/BookShelfTest.php b/tests/Entity/BookShelfTest.php index 4461c0489..748f63da8 100644 --- a/tests/Entity/BookShelfTest.php +++ b/tests/Entity/BookShelfTest.php @@ -45,7 +45,7 @@ class BookShelfTest extends TestCase $resp = $this->actingAs($user)->get('/'); $this->withHtml($resp)->assertElementNotContains('header', 'Shelves'); - $this->setEntityRestrictions($shelf, ['view'], [$userRole]); + $this->entities->setPermissions($shelf, ['view'], [$userRole]); $resp = $this->get('/'); $this->withHtml($resp)->assertElementContains('header', 'Shelves'); @@ -69,7 +69,7 @@ class BookShelfTest extends TestCase $resp->assertSee($book->name); $resp->assertSee($book->getUrl()); - $this->setEntityRestrictions($book, []); + $this->entities->setPermissions($book, []); $resp = $this->asEditor()->get('/shelves'); $resp->assertDontSee($book->name); @@ -298,7 +298,7 @@ class BookShelfTest extends TestCase $this->assertFalse(boolval($child->restricted), 'Child book should not be restricted by default'); $this->assertTrue($child->permissions()->count() === 0, 'Child book should have no permissions by default'); - $this->setEntityRestrictions($shelf, ['view', 'update'], [$editorRole]); + $this->entities->setPermissions($shelf, ['view', 'update'], [$editorRole]); $resp = $this->post($shelf->getUrl('/copy-permissions')); $child = $shelf->books()->first(); diff --git a/tests/Entity/BookTest.php b/tests/Entity/BookTest.php index 2f04fcf25..ec430ae84 100644 --- a/tests/Entity/BookTest.php +++ b/tests/Entity/BookTest.php @@ -246,13 +246,13 @@ class BookTest extends TestCase public function test_slug_multi_byte_url_safe() { - $book = $this->newBook([ + $book = $this->entities->newBook([ 'name' => 'информация', ]); $this->assertEquals('informaciya', $book->slug); - $book = $this->newBook([ + $book = $this->entities->newBook([ 'name' => '¿Qué?', ]); @@ -261,7 +261,7 @@ class BookTest extends TestCase public function test_slug_format() { - $book = $this->newBook([ + $book = $this->entities->newBook([ 'name' => 'PartA / PartB / PartC', ]); @@ -311,7 +311,7 @@ class BookTest extends TestCase foreach ($book->getDirectChildren() as $child) { $child->restricted = true; $child->save(); - $this->regenEntityPermissions($child); + $this->entities->regenPermissions($child); } $this->asEditor()->post($book->getUrl('/copy'), ['name' => 'My copy book']); @@ -365,7 +365,7 @@ class BookTest extends TestCase $viewer = $this->getViewer(); $this->giveUserPermissions($viewer, ['book-update-all', 'book-create-all', 'bookshelf-update-all']); - $this->setEntityRestrictions($shelfB); + $this->entities->setPermissions($shelfB); $this->asEditor()->post($book->getUrl('/copy'), ['name' => 'My copy book']); diff --git a/tests/Entity/ChapterTest.php b/tests/Entity/ChapterTest.php index d58b83da9..c1c746102 100644 --- a/tests/Entity/ChapterTest.php +++ b/tests/Entity/ChapterTest.php @@ -107,7 +107,7 @@ class ChapterTest extends TestCase foreach ($chapter->pages as $page) { $page->restricted = true; $page->save(); - $this->regenEntityPermissions($page); + $this->entities->regenPermissions($page); } $this->asEditor()->post($chapter->getUrl('/copy'), [ diff --git a/tests/Entity/EntityAccessTest.php b/tests/Entity/EntityAccessTest.php index f2f244538..e3d129d5e 100644 --- a/tests/Entity/EntityAccessTest.php +++ b/tests/Entity/EntityAccessTest.php @@ -14,7 +14,7 @@ class EntityAccessTest extends TestCase // Create required assets and revisions $creator = $this->getEditor(); $updater = $this->getViewer(); - $entities = $this->createEntityChainBelongingToUser($creator, $updater); + $entities = $this->entities->createChainBelongingToUser($creator, $updater); app()->make(UserRepo::class)->destroy($creator); app()->make(PageRepo::class)->update($entities['page'], ['html' => '

hello!

>']); @@ -26,7 +26,7 @@ class EntityAccessTest extends TestCase // Create required assets and revisions $creator = $this->getViewer(); $updater = $this->getEditor(); - $entities = $this->createEntityChainBelongingToUser($creator, $updater); + $entities = $this->entities->createChainBelongingToUser($creator, $updater); app()->make(UserRepo::class)->destroy($updater); app()->make(PageRepo::class)->update($entities['page'], ['html' => '

Hello there!

']); diff --git a/tests/Entity/EntitySearchTest.php b/tests/Entity/EntitySearchTest.php index 3a9b9f31b..eabcf6f76 100644 --- a/tests/Entity/EntitySearchTest.php +++ b/tests/Entity/EntitySearchTest.php @@ -47,7 +47,7 @@ class EntitySearchTest extends TestCase public function test_searching_accents_and_small_terms() { - $page = $this->newPage(['name' => 'My new test quaffleachits', 'html' => 'some áéííúü¿¡ test content a2 orange dog']); + $page = $this->entities->newPage(['name' => 'My new test quaffleachits', 'html' => 'some áéííúü¿¡ test content a2 orange dog']); $this->asEditor(); $accentSearch = $this->get('/search?term=' . urlencode('áéíí')); @@ -111,7 +111,7 @@ class EntitySearchTest extends TestCase public function test_exact_searches() { - $page = $this->newPage(['name' => 'My new test page', 'html' => 'this is a story about an orange donkey']); + $page = $this->entities->newPage(['name' => 'My new test page', 'html' => 'this is a story about an orange donkey']); $exactSearchA = $this->asEditor()->get('/search?term=' . urlencode('"story about an orange"')); $exactSearchA->assertStatus(200)->assertSee($page->name); @@ -123,7 +123,7 @@ class EntitySearchTest extends TestCase public function test_search_terms_with_delimiters_are_converted_to_exact_matches() { $this->asEditor(); - $page = $this->newPage(['name' => 'Delimiter test', 'html' => '

1.1 2,2 3?3 4:4 5;5 (8) <9> "10" \'11\' `12`

']); + $page = $this->entities->newPage(['name' => 'Delimiter test', 'html' => '

1.1 2,2 3?3 4:4 5;5 (8) <9> "10" \'11\' `12`

']); $terms = explode(' ', '1.1 2,2 3?3 4:4 5;5 (8) <9> "10" \'11\' `12`'); foreach ($terms as $term) { @@ -134,7 +134,7 @@ class EntitySearchTest extends TestCase public function test_search_filters() { - $page = $this->newPage(['name' => 'My new test quaffleachits', 'html' => 'this is about an orange donkey danzorbhsing']); + $page = $this->entities->newPage(['name' => 'My new test quaffleachits', 'html' => 'this is about an orange donkey danzorbhsing']); $this->asEditor(); $editorId = $this->getEditor()->id; $editorSlug = $this->getEditor()->slug; @@ -197,7 +197,7 @@ class EntitySearchTest extends TestCase public function test_ajax_entity_search() { - $page = $this->newPage(['name' => 'my ajax search test', 'html' => 'ajax test']); + $page = $this->entities->newPage(['name' => 'my ajax search test', 'html' => 'ajax test']); $notVisitedPage = Page::first(); // Visit the page to make popular @@ -334,15 +334,15 @@ class EntitySearchTest extends TestCase public function test_search_ranks_common_words_lower() { - $this->newPage(['name' => 'Test page A', 'html' => '

dog biscuit dog dog

']); - $this->newPage(['name' => 'Test page B', 'html' => '

cat biscuit

']); + $this->entities->newPage(['name' => 'Test page A', 'html' => '

dog biscuit dog dog

']); + $this->entities->newPage(['name' => 'Test page B', 'html' => '

cat biscuit

']); $search = $this->asEditor()->get('/search?term=cat+dog+biscuit'); $this->withHtml($search)->assertElementContains('.entity-list > .page:nth-child(1)', 'Test page A'); $this->withHtml($search)->assertElementContains('.entity-list > .page:nth-child(2)', 'Test page B'); for ($i = 0; $i < 2; $i++) { - $this->newPage(['name' => 'Test page ' . $i, 'html' => '

dog

']); + $this->entities->newPage(['name' => 'Test page ' . $i, 'html' => '

dog

']); } $search = $this->asEditor()->get('/search?term=cat+dog+biscuit'); @@ -352,7 +352,7 @@ class EntitySearchTest extends TestCase public function test_terms_in_headers_have_an_adjusted_index_score() { - $page = $this->newPage(['name' => 'Test page A', 'html' => ' + $page = $this->entities->newPage(['name' => 'Test page A', 'html' => '

TermA

TermB TermNested

TermC

@@ -377,7 +377,7 @@ class EntitySearchTest extends TestCase public function test_name_and_content_terms_are_merged_to_single_score() { - $page = $this->newPage(['name' => 'TermA', 'html' => ' + $page = $this->entities->newPage(['name' => 'TermA', 'html' => '

TermA

']); @@ -389,7 +389,7 @@ class EntitySearchTest extends TestCase public function test_tag_names_and_values_are_indexed_for_search() { - $page = $this->newPage(['name' => 'PageA', 'html' => '

content

', 'tags' => [ + $page = $this->entities->newPage(['name' => 'PageA', 'html' => '

content

', 'tags' => [ ['name' => 'Animal', 'value' => 'MeowieCat'], ['name' => 'SuperImportant'], ]]); @@ -402,7 +402,7 @@ class EntitySearchTest extends TestCase public function test_matching_terms_in_search_results_are_highlighted() { - $this->newPage(['name' => 'My Meowie Cat', 'html' => '

A superimportant page about meowieable animals

', 'tags' => [ + $this->entities->newPage(['name' => 'My Meowie Cat', 'html' => '

A superimportant page about meowieable animals

', 'tags' => [ ['name' => 'Animal', 'value' => 'MeowieCat'], ['name' => 'SuperImportant'], ]]); @@ -420,7 +420,7 @@ class EntitySearchTest extends TestCase public function test_match_highlighting_works_with_multibyte_content() { - $this->newPage([ + $this->entities->newPage([ 'name' => 'Test Page', 'html' => '

На мен ми трябва нещо добро test

', ]); @@ -431,7 +431,7 @@ class EntitySearchTest extends TestCase public function test_html_entities_in_item_details_remains_escaped_in_search_results() { - $this->newPage(['name' => 'My TestPageContent', 'html' => '

My supercool <great> TestPageContent page

']); + $this->entities->newPage(['name' => 'My TestPageContent', 'html' => '

My supercool <great> TestPageContent page

']); $search = $this->asEditor()->get('/search?term=TestPageContent'); $search->assertSee('My <cool> TestPageContent', false); @@ -440,7 +440,7 @@ class EntitySearchTest extends TestCase public function test_words_adjacent_to_lines_breaks_can_be_matched_with_normal_terms() { - $page = $this->newPage(['name' => 'TermA', 'html' => ' + $page = $this->entities->newPage(['name' => 'TermA', 'html' => '

TermA
TermB
TermC

']); diff --git a/tests/Entity/PageTest.php b/tests/Entity/PageTest.php index 734516e87..0f906460b 100644 --- a/tests/Entity/PageTest.php +++ b/tests/Entity/PageTest.php @@ -201,7 +201,7 @@ class PageTest extends TestCase $newBook->owned_by = $viewer->id; $newBook->save(); $this->giveUserPermissions($viewer, ['page-create-own']); - $this->regenEntityPermissions($newBook); + $this->entities->regenPermissions($newBook); $resp = $this->actingAs($viewer)->get($page->getUrl()); $resp->assertSee($page->getUrl('/copy')); @@ -255,7 +255,7 @@ class PageTest extends TestCase public function test_recently_updated_pages_view() { $user = $this->getEditor(); - $content = $this->createEntityChainBelongingToUser($user); + $content = $this->entities->createChainBelongingToUser($user); $resp = $this->asAdmin()->get('/pages/recently-updated'); $this->withHtml($resp)->assertElementContains('.entity-list .page:nth-child(1)', $content['page']->name); @@ -303,8 +303,8 @@ class PageTest extends TestCase 'html' => '

Updated content

', ]); - $this->setEntityRestrictions($page->book); - $this->setEntityRestrictions($page, ['view'], [$user->roles->first()]); + $this->entities->setPermissions($page->book); + $this->entities->setPermissions($page, ['view'], [$user->roles->first()]); $resp = $this->get('/pages/recently-updated'); $resp->assertDontSee($page->book->getShortName(42)); diff --git a/tests/Entity/SortTest.php b/tests/Entity/SortTest.php index 8792e70db..93b668a0e 100644 --- a/tests/Entity/SortTest.php +++ b/tests/Entity/SortTest.php @@ -98,14 +98,14 @@ class SortTest extends TestCase $newBook = Book::query()->where('id', '!=', $currentBook->id)->first(); $editor = $this->getEditor(); - $this->setEntityRestrictions($newBook, ['view', 'update', 'delete'], $editor->roles->all()); + $this->entities->setPermissions($newBook, ['view', 'update', 'delete'], $editor->roles->all()); $movePageResp = $this->actingAs($editor)->put($page->getUrl('/move'), [ 'entity_selection' => 'book:' . $newBook->id, ]); $this->assertPermissionError($movePageResp); - $this->setEntityRestrictions($newBook, ['view', 'update', 'delete', 'create'], $editor->roles->all()); + $this->entities->setPermissions($newBook, ['view', 'update', 'delete', 'create'], $editor->roles->all()); $movePageResp = $this->put($page->getUrl('/move'), [ 'entity_selection' => 'book:' . $newBook->id, ]); @@ -123,8 +123,8 @@ class SortTest extends TestCase $newBook = Book::query()->where('id', '!=', $currentBook->id)->first(); $editor = $this->getEditor(); - $this->setEntityRestrictions($newBook, ['view', 'update', 'create', 'delete'], $editor->roles->all()); - $this->setEntityRestrictions($page, ['view', 'update', 'create'], $editor->roles->all()); + $this->entities->setPermissions($newBook, ['view', 'update', 'create', 'delete'], $editor->roles->all()); + $this->entities->setPermissions($page, ['view', 'update', 'create'], $editor->roles->all()); $movePageResp = $this->actingAs($editor)->put($page->getUrl('/move'), [ 'entity_selection' => 'book:' . $newBook->id, @@ -133,7 +133,7 @@ class SortTest extends TestCase $pageView = $this->get($page->getUrl()); $pageView->assertDontSee($page->getUrl('/move')); - $this->setEntityRestrictions($page, ['view', 'update', 'create', 'delete'], $editor->roles->all()); + $this->entities->setPermissions($page, ['view', 'update', 'create', 'delete'], $editor->roles->all()); $movePageResp = $this->put($page->getUrl('/move'), [ 'entity_selection' => 'book:' . $newBook->id, ]); @@ -178,8 +178,8 @@ class SortTest extends TestCase $newBook = Book::query()->where('id', '!=', $currentBook->id)->first(); $editor = $this->getEditor(); - $this->setEntityRestrictions($newBook, ['view', 'update', 'create', 'delete'], $editor->roles->all()); - $this->setEntityRestrictions($chapter, ['view', 'update', 'create'], $editor->roles->all()); + $this->entities->setPermissions($newBook, ['view', 'update', 'create', 'delete'], $editor->roles->all()); + $this->entities->setPermissions($chapter, ['view', 'update', 'create'], $editor->roles->all()); $moveChapterResp = $this->actingAs($editor)->put($chapter->getUrl('/move'), [ 'entity_selection' => 'book:' . $newBook->id, @@ -188,7 +188,7 @@ class SortTest extends TestCase $pageView = $this->get($chapter->getUrl()); $pageView->assertDontSee($chapter->getUrl('/move')); - $this->setEntityRestrictions($chapter, ['view', 'update', 'create', 'delete'], $editor->roles->all()); + $this->entities->setPermissions($chapter, ['view', 'update', 'create', 'delete'], $editor->roles->all()); $moveChapterResp = $this->put($chapter->getUrl('/move'), [ 'entity_selection' => 'book:' . $newBook->id, ]); @@ -205,15 +205,15 @@ class SortTest extends TestCase $newBook = Book::query()->where('id', '!=', $currentBook->id)->first(); $editor = $this->getEditor(); - $this->setEntityRestrictions($newBook, ['view', 'update', 'delete'], [$editor->roles->first()]); - $this->setEntityRestrictions($chapter, ['view', 'update', 'create', 'delete'], [$editor->roles->first()]); + $this->entities->setPermissions($newBook, ['view', 'update', 'delete'], [$editor->roles->first()]); + $this->entities->setPermissions($chapter, ['view', 'update', 'create', 'delete'], [$editor->roles->first()]); $moveChapterResp = $this->actingAs($editor)->put($chapter->getUrl('/move'), [ 'entity_selection' => 'book:' . $newBook->id, ]); $this->assertPermissionError($moveChapterResp); - $this->setEntityRestrictions($newBook, ['view', 'update', 'create', 'delete'], [$editor->roles->first()]); + $this->entities->setPermissions($newBook, ['view', 'update', 'create', 'delete'], [$editor->roles->first()]); $moveChapterResp = $this->put($chapter->getUrl('/move'), [ 'entity_selection' => 'book:' . $newBook->id, ]); @@ -257,8 +257,8 @@ class SortTest extends TestCase public function test_book_sort() { $oldBook = Book::query()->first(); - $chapterToMove = $this->newChapter(['name' => 'chapter to move'], $oldBook); - $newBook = $this->newBook(['name' => 'New sort book']); + $chapterToMove = $this->entities->newChapter(['name' => 'chapter to move'], $oldBook); + $newBook = $this->entities->newBook(['name' => 'New sort book']); $pagesToMove = Page::query()->take(5)->get(); // Create request data @@ -323,7 +323,7 @@ class SortTest extends TestCase $page = Page::query()->where('chapter_id', '!=', 0)->first(); /** @var Chapter $otherChapter */ $otherChapter = Chapter::query()->where('book_id', '!=', $page->book_id)->first(); - $this->setEntityRestrictions($otherChapter); + $this->entities->setPermissions($otherChapter); $sortData = [ 'id' => $page->id, @@ -346,7 +346,7 @@ class SortTest extends TestCase /** @var Chapter $otherChapter */ $otherChapter = Chapter::query()->where('book_id', '!=', $page->book_id)->first(); $editor = $this->getEditor(); - $this->setEntityRestrictions($otherChapter->book, ['update', 'delete'], [$editor->roles()->first()]); + $this->entities->setPermissions($otherChapter->book, ['update', 'delete'], [$editor->roles()->first()]); $sortData = [ 'id' => $page->id, @@ -369,7 +369,7 @@ class SortTest extends TestCase /** @var Chapter $otherChapter */ $otherChapter = Chapter::query()->where('book_id', '!=', $page->book_id)->first(); $editor = $this->getEditor(); - $this->setEntityRestrictions($otherChapter, ['view', 'delete'], [$editor->roles()->first()]); + $this->entities->setPermissions($otherChapter, ['view', 'delete'], [$editor->roles()->first()]); $sortData = [ 'id' => $page->id, @@ -392,7 +392,7 @@ class SortTest extends TestCase /** @var Chapter $otherChapter */ $otherChapter = Chapter::query()->where('book_id', '!=', $page->book_id)->first(); $editor = $this->getEditor(); - $this->setEntityRestrictions($page, ['view', 'delete'], [$editor->roles()->first()]); + $this->entities->setPermissions($page, ['view', 'delete'], [$editor->roles()->first()]); $sortData = [ 'id' => $page->id, @@ -415,7 +415,7 @@ class SortTest extends TestCase /** @var Chapter $otherChapter */ $otherChapter = Chapter::query()->where('book_id', '!=', $page->book_id)->first(); $editor = $this->getEditor(); - $this->setEntityRestrictions($page, ['view', 'update'], [$editor->roles()->first()]); + $this->entities->setPermissions($page, ['view', 'update'], [$editor->roles()->first()]); $sortData = [ 'id' => $page->id, diff --git a/tests/Entity/TagTest.php b/tests/Entity/TagTest.php index 1d2c9124f..d22dc2f44 100644 --- a/tests/Entity/TagTest.php +++ b/tests/Entity/TagTest.php @@ -188,7 +188,7 @@ class TagTest extends TestCase $resp->assertSee('GreatTestContent'); $page->restricted = true; - $this->regenEntityPermissions($page); + $this->entities->regenPermissions($page); $resp = $this->asEditor()->get('/tags'); $resp->assertDontSee('SuperCategory'); @@ -207,7 +207,7 @@ class TagTest extends TestCase { $this->asEditor(); - foreach ($this->getEachEntityType() as $entity) { + foreach ($this->entities->all() as $entity) { $entity->tags()->create(['name' => 'My Super Tag Name', 'value' => 'An-awesome-value']); $html = $this->withHtml($this->get($entity->getUrl())); $html->assertElementExists('body.tag-name-mysupertagname.tag-value-anawesomevalue.tag-pair-mysupertagname-anawesomevalue'); diff --git a/tests/Helpers/EntityProvider.php b/tests/Helpers/EntityProvider.php new file mode 100644 index 000000000..d3888e71f --- /dev/null +++ b/tests/Helpers/EntityProvider.php @@ -0,0 +1,201 @@ + + */ + protected array $fetchCache = [ + 'book' => [], + 'page' => [], + 'bookshelf' => [], + 'chapter' => [], + ]; + + /** + * Get an un-fetched page from the system. + */ + public function page(): Page + { + /** @var Page $page */ + $page = Page::query()->whereNotIn('id', $this->fetchCache['page'])->first(); + $this->addToCache($page); + return $page; + } + + /** + * Get an un-fetched chapter from the system. + */ + public function chapter(): Chapter + { + /** @var Chapter $chapter */ + $chapter = Chapter::query()->whereNotIn('id', $this->fetchCache['chapter'])->first(); + $this->addToCache($chapter); + return $chapter; + } + + /** + * Get an un-fetched book from the system. + */ + public function book(): Book + { + /** @var Book $book */ + $book = Book::query()->whereNotIn('id', $this->fetchCache['book'])->first(); + $this->addToCache($book); + return $book; + } + + /** + * Get an un-fetched shelf from the system. + */ + public function shelf(): Bookshelf + { + /** @var Bookshelf $shelf */ + $shelf = Bookshelf::query()->whereNotIn('id', $this->fetchCache['bookshelf'])->first(); + $this->addToCache($shelf); + return $shelf; + } + + /** + * Get all entity types from the system. + * @return array{page: Page, chapter: Chapter, book: Book, bookshelf: Bookshelf} + */ + public function all(): array + { + return [ + 'page' => $this->page(), + 'chapter' => $this->chapter(), + 'book' => $this->book(), + 'bookshelf' => $this->shelf(), + ]; + } + + /** + * Create a book to page chain of entities that belong to a specific user. + * @return array{book: Book, chapter: Chapter, page: Page} + */ + public function createChainBelongingToUser(User $creatorUser, ?User $updaterUser = null): array + { + if (empty($updaterUser)) { + $updaterUser = $creatorUser; + } + + $userAttrs = ['created_by' => $creatorUser->id, 'owned_by' => $creatorUser->id, 'updated_by' => $updaterUser->id]; + /** @var Book $book */ + $book = Book::factory()->create($userAttrs); + $chapter = Chapter::factory()->create(array_merge(['book_id' => $book->id], $userAttrs)); + $page = Page::factory()->create(array_merge(['book_id' => $book->id, 'chapter_id' => $chapter->id], $userAttrs)); + + $book->rebuildPermissions(); + $this->addToCache([$page, $chapter, $book]); + + return compact('book', 'chapter', 'page'); + } + + /** + * Create and return a new bookshelf. + */ + public function newShelf(array $input = ['name' => 'test shelf', 'description' => 'My new test shelf']): Bookshelf + { + $shelf = app(BookshelfRepo::class)->create($input, []); + $this->addToCache($shelf); + return $shelf; + } + + /** + * Create and return a new book. + */ + public function newBook(array $input = ['name' => 'test book', 'description' => 'My new test book']): Book + { + $book = app(BookRepo::class)->create($input); + $this->addToCache($book); + return $book; + } + + /** + * Create and return a new test chapter. + */ + public function newChapter(array $input, Book $book): Chapter + { + $chapter = app(ChapterRepo::class)->create($input, $book); + $this->addToCache($chapter); + return $chapter; + } + + /** + * Create and return a new test page. + */ + public function newPage(array $input = ['name' => 'test page', 'html' => 'My new test page']): Page + { + $book = Book::query()->first(); + $pageRepo = app(PageRepo::class); + $draftPage = $pageRepo->getNewDraftPage($book); + $this->addToCache($draftPage); + return $pageRepo->publishDraft($draftPage, $input); + } + + /** + * Regenerate the permission for an entity. + * Centralised to manage clearing of cached elements between requests. + */ + public function regenPermissions(Entity $entity): void + { + $entity->rebuildPermissions(); + $entity->load('jointPermissions'); + } + + /** + * Set the given entity as having restricted permissions, and apply the given + * permissions for the given roles. + * @param string[] $actions + * @param Role[] $roles + */ + public function setPermissions(Entity $entity, array $actions = [], array $roles = []): void + { + $entity->restricted = true; + $entity->permissions()->delete(); + + $permissions = []; + foreach ($actions as $action) { + foreach ($roles as $role) { + $permissions[] = [ + 'role_id' => $role->id, + 'action' => strtolower($action), + ]; + } + } + + $entity->permissions()->createMany($permissions); + $entity->save(); + $entity->load('permissions'); + $this->regenPermissions($entity); + } + + /** + * @param Entity|Entity[] $entities + */ + protected function addToCache($entities): void + { + if (!is_array($entities)) { + $entities = [$entities]; + } + + foreach ($entities as $entity) { + $this->fetchCache[$entity->getType()][] = $entity->id; + } + } +} diff --git a/tests/HomepageTest.php b/tests/HomepageTest.php index 1d968a2c9..bb42f49f2 100644 --- a/tests/HomepageTest.php +++ b/tests/HomepageTest.php @@ -24,7 +24,7 @@ class HomepageTest extends TestCase $this->asEditor(); $name = 'My custom homepage'; $content = str_repeat('This is the body content of my custom homepage.', 20); - $customPage = $this->newPage(['name' => $name, 'html' => $content]); + $customPage = $this->entities->newPage(['name' => $name, 'html' => $content]); $this->setSettings(['app-homepage' => $customPage->id]); $this->setSettings(['app-homepage-type' => 'page']); @@ -41,7 +41,7 @@ class HomepageTest extends TestCase $this->asEditor(); $name = 'My custom homepage'; $content = str_repeat('This is the body content of my custom homepage.', 20); - $customPage = $this->newPage(['name' => $name, 'html' => $content]); + $customPage = $this->entities->newPage(['name' => $name, 'html' => $content]); $this->setSettings([ 'app-homepage' => $customPage->id, 'app-homepage-type' => 'page', @@ -67,7 +67,7 @@ class HomepageTest extends TestCase $this->asEditor(); $name = 'My custom homepage'; $content = str_repeat('This is the body content of my custom homepage.', 20); - $customPage = $this->newPage(['name' => $name, 'html' => $content]); + $customPage = $this->entities->newPage(['name' => $name, 'html' => $content]); $this->setSettings([ 'app-homepage' => $customPage->id, 'app-homepage-type' => 'default', @@ -107,7 +107,7 @@ class HomepageTest extends TestCase $included->save(); $name = 'My custom homepage'; - $customPage = $this->newPage(['name' => $name, 'html' => '{{@' . $included->id . '}}']); + $customPage = $this->entities->newPage(['name' => $name, 'html' => '{{@' . $included->id . '}}']); $this->setSettings(['app-homepage' => $customPage->id]); $this->setSettings(['app-homepage-type' => 'page']); @@ -177,7 +177,7 @@ class HomepageTest extends TestCase $this->withHtml($homeVisit)->assertElementNotContains('.content-wrap', $book->name); // Ensure is visible again with entity-level view permission - $this->setEntityRestrictions($book, ['view'], [$editor->roles()->first()]); + $this->entities->setPermissions($book, ['view'], [$editor->roles()->first()]); $homeVisit = $this->get('/'); $this->withHtml($homeVisit)->assertElementContains('.content-wrap', $shelf->name); $this->withHtml($homeVisit)->assertElementContains('.content-wrap', $book->name); diff --git a/tests/Permissions/EntityPermissionsTest.php b/tests/Permissions/EntityPermissionsTest.php index ed037d3ac..9e80b752a 100644 --- a/tests/Permissions/EntityPermissionsTest.php +++ b/tests/Permissions/EntityPermissionsTest.php @@ -36,7 +36,7 @@ class EntityPermissionsTest extends TestCase $this->user->roles->first(), $this->viewer->roles->first(), ]; - $this->setEntityRestrictions($entity, $actions, $roles); + $this->entities->setPermissions($entity, $actions, $roles); } public function test_bookshelf_view_restriction() diff --git a/tests/Permissions/ExportPermissionsTest.php b/tests/Permissions/ExportPermissionsTest.php index 2e3d84fa1..7e9ce6100 100644 --- a/tests/Permissions/ExportPermissionsTest.php +++ b/tests/Permissions/ExportPermissionsTest.php @@ -27,7 +27,7 @@ class ExportPermissionsTest extends TestCase $resp->assertSee($pageContent); } - $this->setEntityRestrictions($page, []); + $this->entities->setPermissions($page, []); foreach ($formats as $format) { $resp = $this->get($chapter->getUrl("export/{$format}")); @@ -55,7 +55,7 @@ class ExportPermissionsTest extends TestCase $resp->assertSee($pageContent); } - $this->setEntityRestrictions($page, []); + $this->entities->setPermissions($page, []); foreach ($formats as $format) { $resp = $this->get($book->getUrl("export/{$format}")); diff --git a/tests/Permissions/RolesTest.php b/tests/Permissions/RolesTest.php index b992bfecc..a24d5f8d8 100644 --- a/tests/Permissions/RolesTest.php +++ b/tests/Permissions/RolesTest.php @@ -285,7 +285,7 @@ class RolesTest extends TestCase { /** @var Page $otherUsersPage */ $otherUsersPage = Page::query()->first(); - $content = $this->createEntityChainBelongingToUser($this->user); + $content = $this->entities->createChainBelongingToUser($this->user); // Set a different creator on the page we're checking to ensure // that the owner fields are checked @@ -355,9 +355,9 @@ class RolesTest extends TestCase { /** @var Bookshelf $otherShelf */ $otherShelf = Bookshelf::query()->first(); - $ownShelf = $this->newShelf(['name' => 'test-shelf', 'slug' => 'test-shelf']); + $ownShelf = $this->entities->newShelf(['name' => 'test-shelf', 'slug' => 'test-shelf']); $ownShelf->forceFill(['owned_by' => $this->user->id, 'updated_by' => $this->user->id])->save(); - $this->regenEntityPermissions($ownShelf); + $this->entities->regenPermissions($ownShelf); $this->checkAccessPermission('bookshelf-update-own', [ $ownShelf->getUrl('/edit'), @@ -386,9 +386,9 @@ class RolesTest extends TestCase $this->giveUserPermissions($this->user, ['bookshelf-update-all']); /** @var Bookshelf $otherShelf */ $otherShelf = Bookshelf::query()->first(); - $ownShelf = $this->newShelf(['name' => 'test-shelf', 'slug' => 'test-shelf']); + $ownShelf = $this->entities->newShelf(['name' => 'test-shelf', 'slug' => 'test-shelf']); $ownShelf->forceFill(['owned_by' => $this->user->id, 'updated_by' => $this->user->id])->save(); - $this->regenEntityPermissions($ownShelf); + $this->entities->regenPermissions($ownShelf); $this->checkAccessPermission('bookshelf-delete-own', [ $ownShelf->getUrl('/delete'), @@ -438,7 +438,7 @@ class RolesTest extends TestCase { /** @var Book $otherBook */ $otherBook = Book::query()->take(1)->get()->first(); - $ownBook = $this->createEntityChainBelongingToUser($this->user)['book']; + $ownBook = $this->entities->createChainBelongingToUser($this->user)['book']; $this->checkAccessPermission('book-update-own', [ $ownBook->getUrl() . '/edit', ], [ @@ -466,7 +466,7 @@ class RolesTest extends TestCase $this->giveUserPermissions($this->user, ['book-update-all']); /** @var Book $otherBook */ $otherBook = Book::query()->take(1)->get()->first(); - $ownBook = $this->createEntityChainBelongingToUser($this->user)['book']; + $ownBook = $this->entities->createChainBelongingToUser($this->user)['book']; $this->checkAccessPermission('book-delete-own', [ $ownBook->getUrl() . '/delete', ], [ @@ -501,7 +501,7 @@ class RolesTest extends TestCase { /** @var Book $book */ $book = Book::query()->take(1)->get()->first(); - $ownBook = $this->createEntityChainBelongingToUser($this->user)['book']; + $ownBook = $this->entities->createChainBelongingToUser($this->user)['book']; $this->checkAccessPermission('chapter-create-own', [ $ownBook->getUrl('/create-chapter'), ], [ @@ -538,7 +538,7 @@ class RolesTest extends TestCase { /** @var Chapter $otherChapter */ $otherChapter = Chapter::query()->first(); - $ownChapter = $this->createEntityChainBelongingToUser($this->user)['chapter']; + $ownChapter = $this->entities->createChainBelongingToUser($this->user)['chapter']; $this->checkAccessPermission('chapter-update-own', [ $ownChapter->getUrl() . '/edit', ], [ @@ -566,7 +566,7 @@ class RolesTest extends TestCase $this->giveUserPermissions($this->user, ['chapter-update-all']); /** @var Chapter $otherChapter */ $otherChapter = Chapter::query()->first(); - $ownChapter = $this->createEntityChainBelongingToUser($this->user)['chapter']; + $ownChapter = $this->entities->createChainBelongingToUser($this->user)['chapter']; $this->checkAccessPermission('chapter-delete-own', [ $ownChapter->getUrl() . '/delete', ], [ @@ -608,7 +608,7 @@ class RolesTest extends TestCase /** @var Chapter $chapter */ $chapter = Chapter::query()->first(); - $entities = $this->createEntityChainBelongingToUser($this->user); + $entities = $this->entities->createChainBelongingToUser($this->user); $ownBook = $entities['book']; $ownChapter = $entities['chapter']; @@ -699,7 +699,7 @@ class RolesTest extends TestCase { /** @var Page $otherPage */ $otherPage = Page::query()->first(); - $ownPage = $this->createEntityChainBelongingToUser($this->user)['page']; + $ownPage = $this->entities->createChainBelongingToUser($this->user)['page']; $this->checkAccessPermission('page-update-own', [ $ownPage->getUrl() . '/edit', ], [ @@ -727,7 +727,7 @@ class RolesTest extends TestCase $this->giveUserPermissions($this->user, ['page-update-all']); /** @var Page $otherPage */ $otherPage = Page::query()->first(); - $ownPage = $this->createEntityChainBelongingToUser($this->user)['page']; + $ownPage = $this->entities->createChainBelongingToUser($this->user)['page']; $this->checkAccessPermission('page-delete-own', [ $ownPage->getUrl() . '/delete', ], [ @@ -865,14 +865,14 @@ class RolesTest extends TestCase $admin = $this->getAdmin(); // Book links $book = Book::factory()->create(['created_by' => $admin->id, 'updated_by' => $admin->id]); - $this->regenEntityPermissions($book); + $this->entities->regenPermissions($book); $this->actingAs($this->getViewer())->get($book->getUrl()) ->assertDontSee('Create a new page') ->assertDontSee('Add a chapter'); // Chapter links $chapter = Chapter::factory()->create(['created_by' => $admin->id, 'updated_by' => $admin->id, 'book_id' => $book->id]); - $this->regenEntityPermissions($chapter); + $this->entities->regenPermissions($chapter); $this->actingAs($this->getViewer())->get($chapter->getUrl()) ->assertDontSee('Create a new page') ->assertDontSee('Sort the current book'); @@ -880,7 +880,7 @@ class RolesTest extends TestCase public function test_comment_create_permission() { - $ownPage = $this->createEntityChainBelongingToUser($this->user)['page']; + $ownPage = $this->entities->createChainBelongingToUser($this->user)['page']; $this->actingAs($this->user) ->addComment($ownPage) @@ -895,7 +895,7 @@ class RolesTest extends TestCase public function test_comment_update_own_permission() { - $ownPage = $this->createEntityChainBelongingToUser($this->user)['page']; + $ownPage = $this->entities->createChainBelongingToUser($this->user)['page']; $this->giveUserPermissions($this->user, ['comment-create-all']); $this->actingAs($this->user)->addComment($ownPage); /** @var Comment $comment */ @@ -913,7 +913,7 @@ class RolesTest extends TestCase public function test_comment_update_all_permission() { /** @var Page $ownPage */ - $ownPage = $this->createEntityChainBelongingToUser($this->user)['page']; + $ownPage = $this->entities->createChainBelongingToUser($this->user)['page']; $this->asAdmin()->addComment($ownPage); /** @var Comment $comment */ $comment = $ownPage->comments()->latest()->first(); @@ -930,7 +930,7 @@ class RolesTest extends TestCase public function test_comment_delete_own_permission() { /** @var Page $ownPage */ - $ownPage = $this->createEntityChainBelongingToUser($this->user)['page']; + $ownPage = $this->entities->createChainBelongingToUser($this->user)['page']; $this->giveUserPermissions($this->user, ['comment-create-all']); $this->actingAs($this->user)->addComment($ownPage); @@ -949,7 +949,7 @@ class RolesTest extends TestCase public function test_comment_delete_all_permission() { /** @var Page $ownPage */ - $ownPage = $this->createEntityChainBelongingToUser($this->user)['page']; + $ownPage = $this->entities->createChainBelongingToUser($this->user)['page']; $this->asAdmin()->addComment($ownPage); /** @var Comment $comment */ $comment = $ownPage->comments()->latest()->first(); diff --git a/tests/PublicActionTest.php b/tests/PublicActionTest.php index 178e23e2e..309e09600 100644 --- a/tests/PublicActionTest.php +++ b/tests/PublicActionTest.php @@ -177,7 +177,7 @@ class PublicActionTest extends TestCase $this->setSettings(['app-public' => 'true']); /** @var Book $book */ $book = Book::query()->first(); - $this->setEntityRestrictions($book); + $this->entities->setPermissions($book); $resp = $this->get($book->getUrl()); $resp->assertSee('Book not found'); diff --git a/tests/References/CrossLinkParserTest.php b/tests/References/CrossLinkParserTest.php index 856b699c3..43b8a36ae 100644 --- a/tests/References/CrossLinkParserTest.php +++ b/tests/References/CrossLinkParserTest.php @@ -10,7 +10,7 @@ class CrossLinkParserTest extends TestCase { public function test_instance_with_entity_resolvers_matches_entity_links() { - $entities = $this->getEachEntityType(); + $entities = $this->entities->all(); $otherPage = Page::query()->where('id', '!=', $entities['page']->id)->first(); $html = ' diff --git a/tests/References/ReferencesTest.php b/tests/References/ReferencesTest.php index 3fd68d647..59263ee0c 100644 --- a/tests/References/ReferencesTest.php +++ b/tests/References/ReferencesTest.php @@ -57,7 +57,7 @@ class ReferencesTest extends TestCase public function test_references_to_count_visible_on_entity_show_view() { - $entities = $this->getEachEntityType(); + $entities = $this->entities->all(); /** @var Page $otherPage */ $otherPage = Page::query()->where('id', '!=', $entities['page']->id)->first(); @@ -79,7 +79,7 @@ class ReferencesTest extends TestCase public function test_references_to_visible_on_references_page() { - $entities = $this->getEachEntityType(); + $entities = $this->entities->all(); $this->asEditor(); foreach ($entities as $entity) { $this->createReference($entities['page'], $entity); @@ -101,7 +101,7 @@ class ReferencesTest extends TestCase $pageB = Page::query()->where('id', '!=', $page->id)->first(); $this->createReference($pageB, $page); - $this->setEntityRestrictions($pageB); + $this->entities->setPermissions($pageB); $this->asEditor()->get($page->getUrl('/references'))->assertDontSee($pageB->name); $this->asAdmin()->get($page->getUrl('/references'))->assertSee($pageB->name); diff --git a/tests/TestCase.php b/tests/TestCase.php index 594194168..cc8e57453 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -7,15 +7,7 @@ use BookStack\Auth\Permissions\PermissionsRepo; use BookStack\Auth\Permissions\RolePermission; use BookStack\Auth\Role; use BookStack\Auth\User; -use BookStack\Entities\Models\Book; -use BookStack\Entities\Models\Bookshelf; -use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Entity; -use BookStack\Entities\Models\Page; -use BookStack\Entities\Repos\BookRepo; -use BookStack\Entities\Repos\BookshelfRepo; -use BookStack\Entities\Repos\ChapterRepo; -use BookStack\Entities\Repos\PageRepo; use BookStack\Settings\SettingService; use BookStack\Uploads\HttpFetcher; use GuzzleHttp\Client; @@ -34,6 +26,7 @@ use Monolog\Handler\TestHandler; use Monolog\Logger; use Psr\Http\Client\ClientInterface; use Ssddanbrown\AssertHtml\TestsHtml; +use Tests\Helpers\EntityProvider; abstract class TestCase extends BaseTestCase { @@ -43,6 +36,13 @@ abstract class TestCase extends BaseTestCase protected ?User $admin = null; protected ?User $editor = null; + protected EntityProvider $entities; + + protected function setUp(): void + { + $this->entities = new EntityProvider(); + parent::setUp(); + } /** * The base URL to use while testing the application. @@ -135,51 +135,6 @@ abstract class TestCase extends BaseTestCase return User::query()->where('system_name', '=', null)->get()->last(); } - /** - * Regenerate the permission for an entity. - */ - protected function regenEntityPermissions(Entity $entity): void - { - $entity->rebuildPermissions(); - $entity->load('jointPermissions'); - } - - /** - * Create and return a new bookshelf. - */ - public function newShelf(array $input = ['name' => 'test shelf', 'description' => 'My new test shelf']): Bookshelf - { - return app(BookshelfRepo::class)->create($input, []); - } - - /** - * Create and return a new book. - */ - public function newBook(array $input = ['name' => 'test book', 'description' => 'My new test book']): Book - { - return app(BookRepo::class)->create($input); - } - - /** - * Create and return a new test chapter. - */ - public function newChapter(array $input, Book $book): Chapter - { - return app(ChapterRepo::class)->create($input, $book); - } - - /** - * Create and return a new test page. - */ - public function newPage(array $input = ['name' => 'test page', 'html' => 'My new test page']): Page - { - $book = Book::query()->first(); - $pageRepo = app(PageRepo::class); - $draftPage = $pageRepo->getNewDraftPage($book); - - return $pageRepo->publishDraft($draftPage, $input); - } - /** * Quickly sets an array of settings. */ @@ -191,31 +146,6 @@ abstract class TestCase extends BaseTestCase } } - /** - * Manually set some permissions on an entity. - */ - protected function setEntityRestrictions(Entity $entity, array $actions = [], array $roles = []): void - { - $entity->restricted = true; - $entity->permissions()->delete(); - - $permissions = []; - foreach ($actions as $action) { - foreach ($roles as $role) { - $permissions[] = [ - 'role_id' => $role->id, - 'action' => strtolower($action), - ]; - } - } - $entity->permissions()->createMany($permissions); - - $entity->save(); - $entity->load('permissions'); - $this->app->make(JointPermissionBuilder::class)->rebuildForEntity($entity); - $entity->load('jointPermissions'); - } - /** * Give the given user some permissions. */ @@ -262,27 +192,6 @@ abstract class TestCase extends BaseTestCase return $permissionRepo->saveNewRole($roleData); } - /** - * Create a group of entities that belong to a specific user. - * - * @return array{book: Book, chapter: Chapter, page: Page} - */ - protected function createEntityChainBelongingToUser(User $creatorUser, ?User $updaterUser = null): array - { - if (empty($updaterUser)) { - $updaterUser = $creatorUser; - } - - $userAttrs = ['created_by' => $creatorUser->id, 'owned_by' => $creatorUser->id, 'updated_by' => $updaterUser->id]; - $book = Book::factory()->create($userAttrs); - $chapter = Chapter::factory()->create(array_merge(['book_id' => $book->id], $userAttrs)); - $page = Page::factory()->create(array_merge(['book_id' => $book->id, 'chapter_id' => $chapter->id], $userAttrs)); - - $this->app->make(JointPermissionBuilder::class)->rebuildForEntity($book); - - return compact('book', 'chapter', 'page'); - } - /** * Mock the HttpFetcher service and return the given data on fetch. */ @@ -460,17 +369,4 @@ abstract class TestCase extends BaseTestCase $this->assertDatabaseHas('activities', $detailsToCheck); } - - /** - * @return array{page: Page, chapter: Chapter, book: Book, bookshelf: Bookshelf} - */ - protected function getEachEntityType(): array - { - return [ - 'page' => Page::query()->first(), - 'chapter' => Chapter::query()->first(), - 'book' => Book::query()->first(), - 'bookshelf' => Bookshelf::query()->first(), - ]; - } } diff --git a/tests/Uploads/ImageTest.php b/tests/Uploads/ImageTest.php index 84f9e47f4..184da214c 100644 --- a/tests/Uploads/ImageTest.php +++ b/tests/Uploads/ImageTest.php @@ -342,7 +342,7 @@ class ImageTest extends TestCase $this->get($expectedUrl)->assertOk(); - $this->setEntityRestrictions($page, [], []); + $this->entities->setPermissions($page, [], []); $resp = $this->get($expectedUrl); $resp->assertNotFound(); @@ -367,7 +367,7 @@ class ImageTest extends TestCase $this->get($expectedUrl)->assertOk(); - $this->setEntityRestrictions($page, [], []); + $this->entities->setPermissions($page, [], []); $resp = $this->get($expectedUrl); $resp->assertNotFound(); @@ -400,7 +400,7 @@ class ImageTest extends TestCase $export = $this->get($pageB->getUrl('/export/html')); $this->assertStringContainsString($encodedImageContent, $export->getContent()); - $this->setEntityRestrictions($pageA, [], []); + $this->entities->setPermissions($pageA, [], []); $export = $this->get($pageB->getUrl('/export/html')); $this->assertStringNotContainsString($encodedImageContent, $export->getContent()); diff --git a/tests/User/UserProfileTest.php b/tests/User/UserProfileTest.php index e6136962a..77f1644a5 100644 --- a/tests/User/UserProfileTest.php +++ b/tests/User/UserProfileTest.php @@ -29,7 +29,7 @@ class UserProfileTest extends TestCase public function test_profile_page_shows_recent_entities() { - $content = $this->createEntityChainBelongingToUser($this->user, $this->user); + $content = $this->entities->createChainBelongingToUser($this->user, $this->user); $resp = $this->asAdmin()->get('/user/' . $this->user->slug); // Check the recently created page is shown @@ -50,7 +50,7 @@ class UserProfileTest extends TestCase ->assertElementContains('#content-counts', '0 Chapters') ->assertElementContains('#content-counts', '0 Pages'); - $this->createEntityChainBelongingToUser($newUser, $newUser); + $this->entities->createChainBelongingToUser($newUser, $newUser); $resp = $this->asAdmin()->get('/user/' . $newUser->slug) ->assertSee($newUser->name); @@ -63,7 +63,7 @@ class UserProfileTest extends TestCase { $newUser = User::factory()->create(); $this->actingAs($newUser); - $entities = $this->createEntityChainBelongingToUser($newUser, $newUser); + $entities = $this->entities->createChainBelongingToUser($newUser, $newUser); Activity::add(ActivityType::BOOK_UPDATE, $entities['book']); Activity::add(ActivityType::PAGE_CREATE, $entities['page']); @@ -77,7 +77,7 @@ class UserProfileTest extends TestCase { $newUser = User::factory()->create(); $this->actingAs($newUser); - $entities = $this->createEntityChainBelongingToUser($newUser, $newUser); + $entities = $this->entities->createChainBelongingToUser($newUser, $newUser); Activity::add(ActivityType::BOOK_UPDATE, $entities['book']); Activity::add(ActivityType::PAGE_CREATE, $entities['page']); From b56f7355aa3a44c8529073fc2a3e760d0404f2ad Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 29 Sep 2022 17:31:38 +0100 Subject: [PATCH 21/55] Migrated much test entity usage via find/replace --- tests/Actions/AuditLogTest.php | 26 +++---- tests/Actions/WebhookCallTest.php | 3 +- tests/Actions/WebhookFormatTesting.php | 3 +- tests/Api/AttachmentsApiTest.php | 44 ++++------- tests/Api/ChaptersApiTest.php | 4 +- tests/Api/PagesApiTest.php | 4 +- tests/Api/RecycleBinApiTest.php | 8 +- tests/Api/SearchApiTest.php | 6 +- tests/Auth/AuthTest.php | 3 +- tests/Commands/ClearActivityCommandTest.php | 3 +- .../CopyShelfPermissionsCommandTest.php | 2 +- .../RegenerateReferencesCommandTest.php | 3 +- tests/Commands/UpdateUrlCommandTest.php | 2 +- tests/Entity/BookShelfTest.php | 7 +- tests/Entity/BookTest.php | 23 ++---- tests/Entity/ChapterTest.php | 12 +-- tests/Entity/ConvertTest.php | 11 +-- tests/Entity/EntitySearchTest.php | 7 +- tests/Entity/ExportTest.php | 49 ++++++------ tests/Entity/PageContentTest.php | 71 ++++++++---------- tests/Entity/PageDraftTest.php | 21 ++---- tests/Entity/PageEditorTest.php | 18 ++--- tests/Entity/PageRevisionTest.php | 12 +-- tests/Entity/PageTest.php | 21 ++---- tests/Entity/SortTest.php | 14 ++-- tests/Entity/TagTest.php | 20 ++--- tests/ErrorTest.php | 2 +- tests/FavouriteTest.php | 13 ++-- tests/Helpers/EntityProvider.php | 2 +- tests/HomepageTest.php | 5 +- tests/OpenGraphTest.php | 8 +- tests/Permissions/EntityOwnerChangeTest.php | 8 +- tests/Permissions/EntityPermissionsTest.php | 75 +++++++------------ tests/Permissions/ExportPermissionsTest.php | 4 +- tests/Permissions/RolesTest.php | 24 ++---- tests/PublicActionTest.php | 15 ++-- tests/References/CrossLinkParserTest.php | 2 +- tests/References/ReferencesTest.php | 11 ++- tests/Settings/RecycleBinTest.php | 6 +- tests/ThemeTest.php | 9 +-- tests/Uploads/AttachmentTest.php | 24 +++--- tests/Uploads/ImageTest.php | 34 ++++----- tests/Uploads/UsesImages.php | 2 +- tests/User/UserManagementTest.php | 2 +- tests/User/UserPreferencesTest.php | 5 +- 45 files changed, 264 insertions(+), 384 deletions(-) diff --git a/tests/Actions/AuditLogTest.php b/tests/Actions/AuditLogTest.php index 8fbf66e76..f4eebb364 100644 --- a/tests/Actions/AuditLogTest.php +++ b/tests/Actions/AuditLogTest.php @@ -46,7 +46,7 @@ class AuditLogTest extends TestCase { $admin = $this->getAdmin(); $this->actingAs($admin); - $page = Page::query()->first(); + $page = $this->entities->page(); $this->activityService->add(ActivityType::PAGE_CREATE, $page); $activity = Activity::query()->orderBy('id', 'desc')->first(); @@ -60,7 +60,7 @@ class AuditLogTest extends TestCase public function test_shows_name_for_deleted_items() { $this->actingAs($this->getAdmin()); - $page = Page::query()->first(); + $page = $this->entities->page(); $pageName = $page->name; $this->activityService->add(ActivityType::PAGE_CREATE, $page); @@ -76,7 +76,7 @@ class AuditLogTest extends TestCase { $viewer = $this->getViewer(); $this->actingAs($viewer); - $page = Page::query()->first(); + $page = $this->entities->page(); $this->activityService->add(ActivityType::PAGE_CREATE, $page); $this->actingAs($this->getAdmin()); @@ -89,7 +89,7 @@ class AuditLogTest extends TestCase public function test_filters_by_key() { $this->actingAs($this->getAdmin()); - $page = Page::query()->first(); + $page = $this->entities->page(); $this->activityService->add(ActivityType::PAGE_CREATE, $page); $resp = $this->get('settings/audit'); @@ -102,7 +102,7 @@ class AuditLogTest extends TestCase public function test_date_filters() { $this->actingAs($this->getAdmin()); - $page = Page::query()->first(); + $page = $this->entities->page(); $this->activityService->add(ActivityType::PAGE_CREATE, $page); $yesterday = (Carbon::now()->subDay()->format('Y-m-d')); @@ -126,11 +126,11 @@ class AuditLogTest extends TestCase $admin = $this->getAdmin(); $editor = $this->getEditor(); $this->actingAs($admin); - $page = Page::query()->first(); + $page = $this->entities->page(); $this->activityService->add(ActivityType::PAGE_CREATE, $page); $this->actingAs($editor); - $chapter = Chapter::query()->first(); + $chapter = $this->entities->chapter(); $this->activityService->add(ActivityType::CHAPTER_UPDATE, $chapter); $resp = $this->actingAs($admin)->get('settings/audit?user=' . $admin->id); @@ -146,8 +146,7 @@ class AuditLogTest extends TestCase { config()->set('app.proxies', '*'); $editor = $this->getEditor(); - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $this->actingAs($editor)->put($page->getUrl(), [ 'name' => 'Updated page', @@ -171,8 +170,7 @@ class AuditLogTest extends TestCase { config()->set('app.proxies', '*'); $editor = $this->getEditor(); - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $this->actingAs($editor)->put($page->getUrl(), [ 'name' => 'Updated page', @@ -198,8 +196,7 @@ class AuditLogTest extends TestCase config()->set('app.proxies', '*'); config()->set('app.env', 'demo'); $editor = $this->getEditor(); - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $this->actingAs($editor)->put($page->getUrl(), [ 'name' => 'Updated page', @@ -222,8 +219,7 @@ class AuditLogTest extends TestCase config()->set('app.proxies', '*'); config()->set('app.ip_address_precision', 2); $editor = $this->getEditor(); - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $this->actingAs($editor)->put($page->getUrl(), [ 'name' => 'Updated page', diff --git a/tests/Actions/WebhookCallTest.php b/tests/Actions/WebhookCallTest.php index d9f9ddad5..7964fd8af 100644 --- a/tests/Actions/WebhookCallTest.php +++ b/tests/Actions/WebhookCallTest.php @@ -88,8 +88,7 @@ class WebhookCallTest extends TestCase '*' => Http::response('', 200), ]); $webhook = $this->newWebhook(['active' => true, 'endpoint' => 'https://wh.example.com'], ['all']); - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $editor = $this->getEditor(); $this->runEvent(ActivityType::PAGE_UPDATE, $page, $editor); diff --git a/tests/Actions/WebhookFormatTesting.php b/tests/Actions/WebhookFormatTesting.php index 4e9ba5e47..35467a76a 100644 --- a/tests/Actions/WebhookFormatTesting.php +++ b/tests/Actions/WebhookFormatTesting.php @@ -32,8 +32,7 @@ class WebhookFormatTesting extends TestCase public function test_page_create_and_update_events_show_revision_info() { - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $this->asEditor()->put($page->getUrl(), ['name' => 'Updated page', 'html' => 'new page html', 'summary' => 'Update a']); $data = $this->getWebhookData(ActivityType::PAGE_UPDATE, $page); diff --git a/tests/Api/AttachmentsApiTest.php b/tests/Api/AttachmentsApiTest.php index dfd57deb8..c295f7384 100644 --- a/tests/Api/AttachmentsApiTest.php +++ b/tests/Api/AttachmentsApiTest.php @@ -17,7 +17,7 @@ class AttachmentsApiTest extends TestCase public function test_index_endpoint_returns_expected_book() { $this->actingAsApiEditor(); - $page = Page::query()->first(); + $page = $this->entities->page(); $attachment = $this->createAttachmentForPage($page, [ 'name' => 'My test attachment', 'external' => true, @@ -37,8 +37,7 @@ class AttachmentsApiTest extends TestCase public function test_attachments_listing_based_upon_page_visibility() { $this->actingAsApiEditor(); - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $attachment = $this->createAttachmentForPage($page, [ 'name' => 'My test attachment', 'external' => true, @@ -66,8 +65,7 @@ class AttachmentsApiTest extends TestCase public function test_create_endpoint_for_link_attachment() { $this->actingAsApiAdmin(); - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $details = [ 'name' => 'My attachment', @@ -85,8 +83,7 @@ class AttachmentsApiTest extends TestCase public function test_create_endpoint_for_upload_attachment() { $this->actingAsApiAdmin(); - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $file = $this->getTestFile('textfile.txt'); $details = [ @@ -106,8 +103,7 @@ class AttachmentsApiTest extends TestCase public function test_upload_limit_restricts_attachment_uploads() { $this->actingAsApiAdmin(); - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); config()->set('app.upload_limit', 1); @@ -130,8 +126,7 @@ class AttachmentsApiTest extends TestCase public function test_name_needed_to_create() { $this->actingAsApiAdmin(); - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $details = [ 'uploaded_to' => $page->id, @@ -146,8 +141,7 @@ class AttachmentsApiTest extends TestCase public function test_link_or_file_needed_to_create() { $this->actingAsApiAdmin(); - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $details = [ 'name' => 'my attachment', @@ -165,8 +159,7 @@ class AttachmentsApiTest extends TestCase public function test_message_shown_if_file_is_not_a_valid_file() { $this->actingAsApiAdmin(); - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $details = [ 'name' => 'my attachment', @@ -182,8 +175,7 @@ class AttachmentsApiTest extends TestCase public function test_read_endpoint_for_link_attachment() { $this->actingAsApiAdmin(); - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $attachment = $this->createAttachmentForPage($page, [ 'name' => 'my attachment', @@ -216,8 +208,7 @@ class AttachmentsApiTest extends TestCase public function test_read_endpoint_for_file_attachment() { $this->actingAsApiAdmin(); - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $file = $this->getTestFile('textfile.txt'); $details = [ @@ -259,8 +250,7 @@ class AttachmentsApiTest extends TestCase $this->actingAsApiAdmin(); $editor = $this->getEditor(); - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $page->draft = true; $page->owned_by = $editor->id; $page->save(); @@ -280,8 +270,7 @@ class AttachmentsApiTest extends TestCase public function test_update_endpoint() { $this->actingAsApiAdmin(); - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $attachment = $this->createAttachmentForPage($page); $details = [ @@ -298,8 +287,7 @@ class AttachmentsApiTest extends TestCase public function test_update_link_attachment_to_file() { $this->actingAsApiAdmin(); - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $attachment = $this->createAttachmentForPage($page); $file = $this->getTestFile('textfile.txt'); @@ -318,8 +306,7 @@ class AttachmentsApiTest extends TestCase public function test_update_file_attachment_to_link() { $this->actingAsApiAdmin(); - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $file = $this->getTestFile('textfile.txt'); $this->call('POST', $this->baseEndpoint, ['name' => 'My file attachment', 'uploaded_to' => $page->id], [], ['file' => $file]); /** @var Attachment $attachment */ @@ -346,8 +333,7 @@ class AttachmentsApiTest extends TestCase public function test_delete_endpoint() { $this->actingAsApiAdmin(); - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $attachment = $this->createAttachmentForPage($page); $resp = $this->deleteJson("{$this->baseEndpoint}/{$attachment->id}"); diff --git a/tests/Api/ChaptersApiTest.php b/tests/Api/ChaptersApiTest.php index 8d31500eb..22be2482c 100644 --- a/tests/Api/ChaptersApiTest.php +++ b/tests/Api/ChaptersApiTest.php @@ -34,7 +34,7 @@ class ChaptersApiTest extends TestCase public function test_create_endpoint() { $this->actingAsApiEditor(); - $book = Book::query()->first(); + $book = $this->entities->book(); $details = [ 'name' => 'My API chapter', 'description' => 'A chapter created via the API', @@ -64,7 +64,7 @@ class ChaptersApiTest extends TestCase public function test_chapter_name_needed_to_create() { $this->actingAsApiEditor(); - $book = Book::query()->first(); + $book = $this->entities->book(); $details = [ 'book_id' => $book->id, 'description' => 'A chapter created via the API', diff --git a/tests/Api/PagesApiTest.php b/tests/Api/PagesApiTest.php index 20c6977dd..fe1fc8d36 100644 --- a/tests/Api/PagesApiTest.php +++ b/tests/Api/PagesApiTest.php @@ -35,7 +35,7 @@ class PagesApiTest extends TestCase public function test_create_endpoint() { $this->actingAsApiEditor(); - $book = Book::query()->first(); + $book = $this->entities->book(); $details = [ 'name' => 'My API page', 'book_id' => $book->id, @@ -67,7 +67,7 @@ class PagesApiTest extends TestCase public function test_page_name_needed_to_create() { $this->actingAsApiEditor(); - $book = Book::query()->first(); + $book = $this->entities->book(); $details = [ 'book_id' => $book->id, 'html' => '

A page created via the API

', diff --git a/tests/Api/RecycleBinApiTest.php b/tests/Api/RecycleBinApiTest.php index 83cd82480..cdb51f85a 100644 --- a/tests/Api/RecycleBinApiTest.php +++ b/tests/Api/RecycleBinApiTest.php @@ -50,8 +50,8 @@ class RecycleBinApiTest extends TestCase { $admin = $this->getAdmin(); - $page = Page::query()->first(); - $book = Book::query()->first(); + $page = $this->entities->page(); + $book = $this->entities->book(); $this->actingAs($admin)->delete($page->getUrl()); $this->delete($book->getUrl()); @@ -139,7 +139,7 @@ class RecycleBinApiTest extends TestCase public function test_restore_endpoint() { - $page = Page::query()->first(); + $page = $this->entities->page(); $this->asAdmin()->delete($page->getUrl()); $page->refresh(); @@ -163,7 +163,7 @@ class RecycleBinApiTest extends TestCase public function test_destroy_endpoint() { - $page = Page::query()->first(); + $page = $this->entities->page(); $this->asAdmin()->delete($page->getUrl()); $page->refresh(); diff --git a/tests/Api/SearchApiTest.php b/tests/Api/SearchApiTest.php index 1f38c7fd9..cdc954ec3 100644 --- a/tests/Api/SearchApiTest.php +++ b/tests/Api/SearchApiTest.php @@ -38,8 +38,7 @@ class SearchApiTest extends TestCase public function test_all_endpoint_returns_entity_url() { - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $page->update(['name' => 'name with superuniquevalue within']); $page->indexForSearch(); @@ -52,8 +51,7 @@ class SearchApiTest extends TestCase public function test_all_endpoint_returns_items_with_preview_html() { - /** @var Book $book */ - $book = Book::query()->first(); + $book = $this->entities->book(); $book->update(['name' => 'name with superuniquevalue within', 'description' => 'Description with superuniquevalue within']); $book->indexForSearch(); diff --git a/tests/Auth/AuthTest.php b/tests/Auth/AuthTest.php index 849469766..4456ed459 100644 --- a/tests/Auth/AuthTest.php +++ b/tests/Auth/AuthTest.php @@ -58,8 +58,7 @@ class AuthTest extends TestCase public function test_login_redirects_to_initially_requested_url_correctly() { config()->set('app.url', 'http://localhost'); - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $this->get($page->getUrl())->assertRedirect(url('/login')); $this->login('admin@admin.com', 'password') diff --git a/tests/Commands/ClearActivityCommandTest.php b/tests/Commands/ClearActivityCommandTest.php index 71baa0ca6..abc8bc7f4 100644 --- a/tests/Commands/ClearActivityCommandTest.php +++ b/tests/Commands/ClearActivityCommandTest.php @@ -14,8 +14,7 @@ class ClearActivityCommandTest extends TestCase public function test_clear_activity_command() { $this->asEditor(); - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); Activity::add(ActivityType::PAGE_UPDATE, $page); $this->assertDatabaseHas('activities', [ diff --git a/tests/Commands/CopyShelfPermissionsCommandTest.php b/tests/Commands/CopyShelfPermissionsCommandTest.php index dd39317ae..bd96f2cc5 100644 --- a/tests/Commands/CopyShelfPermissionsCommandTest.php +++ b/tests/Commands/CopyShelfPermissionsCommandTest.php @@ -36,7 +36,7 @@ class CopyShelfPermissionsCommandTest extends TestCase public function test_copy_shelf_permissions_command_using_all() { - $shelf = Bookshelf::query()->first(); + $shelf = $this->entities->shelf(); Bookshelf::query()->where('id', '!=', $shelf->id)->delete(); $child = $shelf->books()->first(); $editorRole = $this->getEditor()->roles()->first(); diff --git a/tests/Commands/RegenerateReferencesCommandTest.php b/tests/Commands/RegenerateReferencesCommandTest.php index 27dde749b..2c737712a 100644 --- a/tests/Commands/RegenerateReferencesCommandTest.php +++ b/tests/Commands/RegenerateReferencesCommandTest.php @@ -10,8 +10,7 @@ class RegenerateReferencesCommandTest extends TestCase { public function test_regenerate_references_command() { - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $book = $page->book; $page->html = 'Book Link'; diff --git a/tests/Commands/UpdateUrlCommandTest.php b/tests/Commands/UpdateUrlCommandTest.php index 0acccd80c..c4b09162e 100644 --- a/tests/Commands/UpdateUrlCommandTest.php +++ b/tests/Commands/UpdateUrlCommandTest.php @@ -10,7 +10,7 @@ class UpdateUrlCommandTest extends TestCase { public function test_command_updates_page_content() { - $page = Page::query()->first(); + $page = $this->entities->page(); $page->html = ''; $page->save(); diff --git a/tests/Entity/BookShelfTest.php b/tests/Entity/BookShelfTest.php index 748f63da8..798edeadf 100644 --- a/tests/Entity/BookShelfTest.php +++ b/tests/Entity/BookShelfTest.php @@ -62,7 +62,7 @@ class BookShelfTest extends TestCase config()->set([ 'setting-defaults.user.bookshelves_view_type' => 'list', ]); - $shelf = Bookshelf::query()->first(); + $shelf = $this->entities->shelf(); $book = $shelf->books()->first(); $resp = $this->asEditor()->get('/shelves'); @@ -160,7 +160,7 @@ class BookShelfTest extends TestCase public function test_shelf_view_has_sort_control_that_defaults_to_default() { - $shelf = Bookshelf::query()->first(); + $shelf = $this->entities->shelf(); $resp = $this->asAdmin()->get($shelf->getUrl()); $this->withHtml($resp)->assertElementExists('form[action$="change-sort/shelf_books"]'); $this->withHtml($resp)->assertElementContains('form[action$="change-sort/shelf_books"] [aria-haspopup="true"]', 'Default'); @@ -373,8 +373,7 @@ class BookShelfTest extends TestCase public function test_cancel_on_child_book_creation_returns_to_original_shelf() { - /** @var Bookshelf $shelf */ - $shelf = Bookshelf::query()->first(); + $shelf = $this->entities->shelf(); $resp = $this->asEditor()->get($shelf->getUrl('/create-book')); $this->withHtml($resp)->assertElementContains('form a[href="' . $shelf->getUrl() . '"]', 'Cancel'); } diff --git a/tests/Entity/BookTest.php b/tests/Entity/BookTest.php index ec430ae84..2914162cf 100644 --- a/tests/Entity/BookTest.php +++ b/tests/Entity/BookTest.php @@ -80,8 +80,7 @@ class BookTest extends TestCase public function test_update() { - /** @var Book $book */ - $book = Book::query()->first(); + $book = $this->entities->book(); // Cheeky initial update to refresh slug $this->asEditor()->put($book->getUrl(), ['name' => $book->name . '5', 'description' => $book->description]); $book->refresh(); @@ -104,8 +103,7 @@ class BookTest extends TestCase public function test_update_sets_tags() { - /** @var Book $book */ - $book = Book::query()->first(); + $book = $this->entities->book(); $this->assertEquals(0, $book->tags()->count()); @@ -167,15 +165,14 @@ class BookTest extends TestCase public function test_cancel_on_edit_book_page_leads_back_to_book() { - /** @var Book $book */ - $book = Book::query()->first(); + $book = $this->entities->book(); $resp = $this->asEditor()->get($book->getUrl('/edit')); $this->withHtml($resp)->assertElementContains('form a[href="' . $book->getUrl() . '"]', 'Cancel'); } public function test_next_previous_navigation_controls_show_within_book_content() { - $book = Book::query()->first(); + $book = $this->entities->book(); $chapter = $book->chapters->first(); $resp = $this->asEditor()->get($chapter->getUrl()); @@ -270,8 +267,7 @@ class BookTest extends TestCase public function test_show_view_has_copy_button() { - /** @var Book $book */ - $book = Book::query()->first(); + $book = $this->entities->book(); $resp = $this->asEditor()->get($book->getUrl()); $this->withHtml($resp)->assertElementContains("a[href=\"{$book->getUrl('/copy')}\"]", 'Copy'); @@ -279,8 +275,7 @@ class BookTest extends TestCase public function test_copy_view() { - /** @var Book $book */ - $book = Book::query()->first(); + $book = $this->entities->book(); $resp = $this->asEditor()->get($book->getUrl('/copy')); $resp->assertOk(); @@ -338,8 +333,7 @@ class BookTest extends TestCase public function test_copy_clones_cover_image_if_existing() { - /** @var Book $book */ - $book = Book::query()->first(); + $book = $this->entities->book(); $bookRepo = $this->app->make(BookRepo::class); $coverImageFile = $this->getTestImage('cover.png'); $bookRepo->updateCoverImage($book, $coverImageFile); @@ -357,8 +351,7 @@ class BookTest extends TestCase /** @var Bookshelf $shelfA */ /** @var Bookshelf $shelfB */ [$shelfA, $shelfB] = Bookshelf::query()->take(2)->get(); - /** @var Book $book */ - $book = Book::query()->first(); + $book = $this->entities->book(); $shelfA->appendBook($book); $shelfB->appendBook($book); diff --git a/tests/Entity/ChapterTest.php b/tests/Entity/ChapterTest.php index c1c746102..fc8adb01d 100644 --- a/tests/Entity/ChapterTest.php +++ b/tests/Entity/ChapterTest.php @@ -11,8 +11,7 @@ class ChapterTest extends TestCase { public function test_create() { - /** @var Book $book */ - $book = Book::query()->first(); + $book = $this->entities->book(); $chapter = Chapter::factory()->make([ 'name' => 'My First Chapter', @@ -58,8 +57,7 @@ class ChapterTest extends TestCase public function test_show_view_has_copy_button() { - /** @var Chapter $chapter */ - $chapter = Chapter::query()->first(); + $chapter = $this->entities->chapter(); $resp = $this->asEditor()->get($chapter->getUrl()); $this->withHtml($resp)->assertElementContains("a[href$=\"{$chapter->getUrl('/copy')}\"]", 'Copy'); @@ -67,8 +65,7 @@ class ChapterTest extends TestCase public function test_copy_view() { - /** @var Chapter $chapter */ - $chapter = Chapter::query()->first(); + $chapter = $this->entities->chapter(); $resp = $this->asEditor()->get($chapter->getUrl('/copy')); $resp->assertOk(); @@ -149,8 +146,7 @@ class ChapterTest extends TestCase public function test_sort_book_action_visible_if_permissions_allow() { - /** @var Chapter $chapter */ - $chapter = Chapter::query()->first(); + $chapter = $this->entities->chapter(); $resp = $this->actingAs($this->getViewer())->get($chapter->getUrl()); $this->withHtml($resp)->assertLinkNotExists($chapter->book->getUrl('sort')); diff --git a/tests/Entity/ConvertTest.php b/tests/Entity/ConvertTest.php index 58f694f60..15205c9ad 100644 --- a/tests/Entity/ConvertTest.php +++ b/tests/Entity/ConvertTest.php @@ -14,8 +14,7 @@ class ConvertTest extends TestCase { public function test_chapter_edit_view_shows_convert_option() { - /** @var Chapter $chapter */ - $chapter = Chapter::query()->first(); + $chapter = $this->entities->chapter(); $resp = $this->asEditor()->get($chapter->getUrl('/edit')); $resp->assertSee('Convert to Book'); @@ -50,8 +49,7 @@ class ConvertTest extends TestCase public function test_convert_chapter_to_book_requires_permissions() { - /** @var Chapter $chapter */ - $chapter = Chapter::query()->first(); + $chapter = $this->entities->chapter(); $user = $this->getViewer(); $permissions = ['chapter-delete-all', 'book-create-all', 'chapter-update-all']; @@ -71,7 +69,7 @@ class ConvertTest extends TestCase public function test_book_edit_view_shows_convert_option() { - $book = Book::query()->first(); + $book = $this->entities->book(); $resp = $this->asEditor()->get($book->getUrl('/edit')); $resp->assertSee('Convert to Shelf'); @@ -124,8 +122,7 @@ class ConvertTest extends TestCase public function test_book_convert_to_shelf_requires_permissions() { - /** @var Book $book */ - $book = Book::query()->first(); + $book = $this->entities->book(); $user = $this->getViewer(); $permissions = ['book-delete-all', 'bookshelf-create-all', 'book-update-all', 'book-create-all']; diff --git a/tests/Entity/EntitySearchTest.php b/tests/Entity/EntitySearchTest.php index eabcf6f76..82b97e6f3 100644 --- a/tests/Entity/EntitySearchTest.php +++ b/tests/Entity/EntitySearchTest.php @@ -23,8 +23,7 @@ class EntitySearchTest extends TestCase public function test_bookshelf_search() { - /** @var Bookshelf $shelf */ - $shelf = Bookshelf::query()->first(); + $shelf = $this->entities->shelf(); $search = $this->asEditor()->get('/search?term=' . urlencode($shelf->name) . ' {type:bookshelf}'); $search->assertSee('Search Results'); @@ -232,7 +231,7 @@ class EntitySearchTest extends TestCase public function test_ajax_entity_search_reflects_items_without_permission() { - $page = Page::query()->first(); + $page = $this->entities->page(); $baseSelector = 'a[data-entity-type="page"][data-entity-id="' . $page->id . '"]'; $searchUrl = '/ajax/search/entities?permission=update&term=' . urlencode($page->name); @@ -318,7 +317,7 @@ class EntitySearchTest extends TestCase public function test_search_works_on_updated_page_content() { - $page = Page::query()->first(); + $page = $this->entities->page(); $this->asEditor(); $update = $this->put($page->getUrl(), [ diff --git a/tests/Entity/ExportTest.php b/tests/Entity/ExportTest.php index 0d13d208e..1d4a23560 100644 --- a/tests/Entity/ExportTest.php +++ b/tests/Entity/ExportTest.php @@ -15,7 +15,7 @@ class ExportTest extends TestCase { public function test_page_text_export() { - $page = Page::query()->first(); + $page = $this->entities->page(); $this->asEditor(); $resp = $this->get($page->getUrl('/export/plaintext')); @@ -26,7 +26,7 @@ class ExportTest extends TestCase public function test_page_pdf_export() { - $page = Page::query()->first(); + $page = $this->entities->page(); $this->asEditor(); $resp = $this->get($page->getUrl('/export/pdf')); @@ -36,7 +36,7 @@ class ExportTest extends TestCase public function test_page_html_export() { - $page = Page::query()->first(); + $page = $this->entities->page(); $this->asEditor(); $resp = $this->get($page->getUrl('/export/html')); @@ -47,7 +47,7 @@ class ExportTest extends TestCase public function test_book_text_export() { - $page = Page::query()->first(); + $page = $this->entities->page(); $book = $page->book; $this->asEditor(); @@ -60,7 +60,7 @@ class ExportTest extends TestCase public function test_book_pdf_export() { - $page = Page::query()->first(); + $page = $this->entities->page(); $book = $page->book; $this->asEditor(); @@ -71,7 +71,7 @@ class ExportTest extends TestCase public function test_book_html_export() { - $page = Page::query()->first(); + $page = $this->entities->page(); $book = $page->book; $this->asEditor(); @@ -85,7 +85,7 @@ class ExportTest extends TestCase public function test_book_html_export_shows_chapter_descriptions() { $chapterDesc = 'My custom test chapter description ' . Str::random(12); - $chapter = Chapter::query()->first(); + $chapter = $this->entities->chapter(); $chapter->description = $chapterDesc; $chapter->save(); @@ -98,7 +98,7 @@ class ExportTest extends TestCase public function test_chapter_text_export() { - $chapter = Chapter::query()->first(); + $chapter = $this->entities->chapter(); $page = $chapter->pages[0]; $this->asEditor(); @@ -111,7 +111,7 @@ class ExportTest extends TestCase public function test_chapter_pdf_export() { - $chapter = Chapter::query()->first(); + $chapter = $this->entities->chapter(); $this->asEditor(); $resp = $this->get($chapter->getUrl('/export/pdf')); @@ -121,7 +121,7 @@ class ExportTest extends TestCase public function test_chapter_html_export() { - $chapter = Chapter::query()->first(); + $chapter = $this->entities->chapter(); $page = $chapter->pages[0]; $this->asEditor(); @@ -134,7 +134,7 @@ class ExportTest extends TestCase public function test_page_html_export_contains_custom_head_if_set() { - $page = Page::query()->first(); + $page = $this->entities->page(); $customHeadContent = ''; $this->setSettings(['app-custom-head' => $customHeadContent]); @@ -145,7 +145,7 @@ class ExportTest extends TestCase public function test_page_html_export_does_not_break_with_only_comments_in_custom_head() { - $page = Page::query()->first(); + $page = $this->entities->page(); $customHeadContent = ''; $this->setSettings(['app-custom-head' => $customHeadContent]); @@ -157,7 +157,7 @@ class ExportTest extends TestCase public function test_page_html_export_use_absolute_dates() { - $page = Page::query()->first(); + $page = $this->entities->page(); $resp = $this->asEditor()->get($page->getUrl('/export/html')); $resp->assertSee($page->created_at->formatLocalized('%e %B %Y %H:%M:%S')); @@ -168,7 +168,7 @@ class ExportTest extends TestCase public function test_page_export_does_not_include_user_or_revision_links() { - $page = Page::query()->first(); + $page = $this->entities->page(); $resp = $this->asEditor()->get($page->getUrl('/export/html')); $resp->assertDontSee($page->getUrl('/revisions')); @@ -178,7 +178,7 @@ class ExportTest extends TestCase public function test_page_export_sets_right_data_type_for_svg_embeds() { - $page = Page::query()->first(); + $page = $this->entities->page(); Storage::disk('local')->makeDirectory('uploads/images/gallery'); Storage::disk('local')->put('uploads/images/gallery/svg_test.svg', ''); $page->html = ''; @@ -194,7 +194,7 @@ class ExportTest extends TestCase public function test_page_image_containment_works_on_multiple_images_within_a_single_line() { - $page = Page::query()->first(); + $page = $this->entities->page(); Storage::disk('local')->makeDirectory('uploads/images/gallery'); Storage::disk('local')->put('uploads/images/gallery/svg_test.svg', ''); Storage::disk('local')->put('uploads/images/gallery/svg_test2.svg', ''); @@ -210,7 +210,7 @@ class ExportTest extends TestCase public function test_page_export_contained_html_image_fetches_only_run_when_url_points_to_image_upload_folder() { - $page = Page::query()->first(); + $page = $this->entities->page(); $page->html = '' . '' . ''; @@ -235,7 +235,7 @@ class ExportTest extends TestCase $contents = file_get_contents(public_path('.htaccess')); config()->set('filesystems.images', 'local'); - $page = Page::query()->first(); + $page = $this->entities->page(); $page->html = ''; $page->save(); @@ -249,7 +249,7 @@ class ExportTest extends TestCase config()->set('filesystems.images', 'local_secure'); file_put_contents($testFilePath, 'I am a cat'); - $page = Page::query()->first(); + $page = $this->entities->page(); $page->html = ''; $page->save(); @@ -276,7 +276,7 @@ class ExportTest extends TestCase public function test_page_export_with_deleted_creator_and_updater() { $user = $this->getViewer(['name' => 'ExportWizardTheFifth']); - $page = Page::query()->first(); + $page = $this->entities->page(); $page->created_by = $user->id; $page->updated_by = $user->id; $page->save(); @@ -329,7 +329,7 @@ class ExportTest extends TestCase public function test_page_markdown_export() { - $page = Page::query()->first(); + $page = $this->entities->page(); $resp = $this->asEditor()->get($page->getUrl('/export/markdown')); $resp->assertStatus(200); @@ -364,7 +364,7 @@ class ExportTest extends TestCase public function test_chapter_markdown_export() { - $chapter = Chapter::query()->first(); + $chapter = $this->entities->chapter(); $page = $chapter->pages()->first(); $resp = $this->asEditor()->get($chapter->getUrl('/export/markdown')); @@ -430,8 +430,7 @@ class ExportTest extends TestCase public function test_wkhtmltopdf_only_used_when_allow_untrusted_is_true() { - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); config()->set('snappy.pdf.binary', '/abc123'); config()->set('app.allow_untrusted_server_fetching', false); @@ -460,7 +459,7 @@ class ExportTest extends TestCase public function test_html_exports_contain_body_classes_for_export_identification() { - $page = Page::query()->first(); + $page = $this->entities->page(); $resp = $this->asEditor()->get($page->getUrl('/export/html')); $this->withHtml($resp)->assertElementExists('body.export.export-format-html.export-engine-none'); diff --git a/tests/Entity/PageContentTest.php b/tests/Entity/PageContentTest.php index 1c0519586..c6de4dc51 100644 --- a/tests/Entity/PageContentTest.php +++ b/tests/Entity/PageContentTest.php @@ -15,7 +15,7 @@ class PageContentTest extends TestCase public function test_page_includes() { - $page = Page::query()->first(); + $page = $this->entities->page(); $secondPage = Page::query()->where('id', '!=', $page->id)->first(); $secondPage->html = "

Hello, This is a test

This is a second block of content

"; @@ -44,7 +44,7 @@ class PageContentTest extends TestCase public function test_saving_page_with_includes() { - $page = Page::query()->first(); + $page = $this->entities->page(); $secondPage = Page::query()->where('id', '!=', $page->id)->first(); $this->asEditor(); @@ -62,10 +62,8 @@ class PageContentTest extends TestCase public function test_page_includes_do_not_break_tables() { - /** @var Page $page */ - $page = Page::query()->first(); - /** @var Page $secondPage */ - $secondPage = Page::query()->where('id', '!=', $page->id)->first(); + $page = $this->entities->page(); + $secondPage = $this->entities->page(); $content = '
test
'; $secondPage->html = $content; @@ -80,10 +78,8 @@ class PageContentTest extends TestCase public function test_page_includes_do_not_break_code() { - /** @var Page $page */ - $page = Page::query()->first(); - /** @var Page $secondPage */ - $secondPage = Page::query()->where('id', '!=', $page->id)->first(); + $page = $this->entities->page(); + $secondPage = $this->entities->page(); $content = '
var cat = null;
'; $secondPage->html = $content; @@ -98,7 +94,7 @@ class PageContentTest extends TestCase public function test_page_includes_rendered_on_book_export() { - $page = Page::query()->first(); + $page = $this->entities->page(); $secondPage = Page::query() ->where('book_id', '!=', $page->book_id) ->first(); @@ -118,7 +114,7 @@ class PageContentTest extends TestCase public function test_page_content_scripts_removed_by_default() { $this->asEditor(); - $page = Page::query()->first(); + $page = $this->entities->page(); $script = 'abc123abc123'; $page->html = "escape {$script}"; $page->save(); @@ -141,7 +137,7 @@ class PageContentTest extends TestCase ]; $this->asEditor(); - $page = Page::query()->first(); + $page = $this->entities->page(); foreach ($checks as $check) { $page->html = $check; @@ -177,7 +173,7 @@ class PageContentTest extends TestCase ]; $this->asEditor(); - $page = Page::query()->first(); + $page = $this->entities->page(); foreach ($checks as $check) { $page->html = $check; @@ -206,7 +202,7 @@ class PageContentTest extends TestCase ]; $this->asEditor(); - $page = Page::query()->first(); + $page = $this->entities->page(); foreach ($checks as $check) { $page->html = $check; @@ -230,7 +226,7 @@ class PageContentTest extends TestCase ]; $this->asEditor(); - $page = Page::query()->first(); + $page = $this->entities->page(); foreach ($checks as $check) { $page->html = $check; @@ -255,7 +251,7 @@ class PageContentTest extends TestCase ]; $this->asEditor(); - $page = Page::query()->first(); + $page = $this->entities->page(); foreach ($checks as $check) { $page->html = $check; @@ -273,7 +269,7 @@ class PageContentTest extends TestCase public function test_page_inline_on_attributes_removed_by_default() { $this->asEditor(); - $page = Page::query()->first(); + $page = $this->entities->page(); $script = '

Hello

'; $page->html = "escape {$script}"; $page->save(); @@ -298,7 +294,7 @@ class PageContentTest extends TestCase ]; $this->asEditor(); - $page = Page::query()->first(); + $page = $this->entities->page(); foreach ($checks as $check) { $page->html = $check; @@ -313,7 +309,7 @@ class PageContentTest extends TestCase public function test_page_content_scripts_show_when_configured() { $this->asEditor(); - $page = Page::query()->first(); + $page = $this->entities->page(); config()->push('app.allow_content_scripts', 'true'); $script = 'abc123abc123'; @@ -339,7 +335,7 @@ class PageContentTest extends TestCase ]; $this->asEditor(); - $page = Page::query()->first(); + $page = $this->entities->page(); foreach ($checks as $check) { $page->html = $check; @@ -358,7 +354,7 @@ class PageContentTest extends TestCase public function test_page_inline_on_attributes_show_if_configured() { $this->asEditor(); - $page = Page::query()->first(); + $page = $this->entities->page(); config()->push('app.allow_content_scripts', 'true'); $script = '

Hello

'; @@ -390,7 +386,7 @@ class PageContentTest extends TestCase public function test_duplicate_ids_fixed_on_page_save() { $this->asEditor(); - $page = Page::query()->first(); + $page = $this->entities->page(); $content = '
  • test a
    • test b
'; $pageSave = $this->put($page->getUrl(), [ @@ -407,7 +403,7 @@ class PageContentTest extends TestCase public function test_anchors_referencing_non_bkmrk_ids_rewritten_after_save() { $this->asEditor(); - $page = Page::query()->first(); + $page = $this->entities->page(); $content = '

test

link

'; $this->put($page->getUrl(), [ @@ -485,7 +481,7 @@ class PageContentTest extends TestCase public function test_page_text_decodes_html_entities() { - $page = Page::query()->first(); + $page = $this->entities->page(); $this->actingAs($this->getAdmin()) ->put($page->getUrl(''), [ @@ -500,7 +496,7 @@ class PageContentTest extends TestCase public function test_page_markdown_table_rendering() { $this->asEditor(); - $page = Page::query()->first(); + $page = $this->entities->page(); $content = '| Syntax | Description | | ----------- | ----------- | @@ -521,7 +517,7 @@ class PageContentTest extends TestCase public function test_page_markdown_task_list_rendering() { $this->asEditor(); - $page = Page::query()->first(); + $page = $this->entities->page(); $content = '- [ ] Item a - [x] Item b'; @@ -542,7 +538,7 @@ class PageContentTest extends TestCase public function test_page_markdown_strikethrough_rendering() { $this->asEditor(); - $page = Page::query()->first(); + $page = $this->entities->page(); $content = '~~some crossed out text~~'; $this->put($page->getUrl(), [ @@ -560,7 +556,7 @@ class PageContentTest extends TestCase public function test_page_markdown_single_html_comment_saving() { $this->asEditor(); - $page = Page::query()->first(); + $page = $this->entities->page(); $content = ''; $this->put($page->getUrl(), [ @@ -579,7 +575,7 @@ class PageContentTest extends TestCase public function test_base64_images_get_extracted_from_page_content() { $this->asEditor(); - $page = Page::query()->first(); + $page = $this->entities->page(); $this->put($page->getUrl(), [ 'name' => $page->name, 'summary' => '', @@ -601,7 +597,7 @@ class PageContentTest extends TestCase public function test_base64_images_get_extracted_when_containing_whitespace() { $this->asEditor(); - $page = Page::query()->first(); + $page = $this->entities->page(); $base64PngWithWhitespace = "iVBORw0KGg\noAAAANSUhE\tUgAAAAEAAAA BCA YAAAAfFcSJAAA\n\t ACklEQVR4nGMAAQAABQAB"; $base64PngWithoutWhitespace = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQAB'; @@ -632,7 +628,7 @@ class PageContentTest extends TestCase foreach ($extensions as $extension) { $this->asEditor(); - $page = Page::query()->first(); + $page = $this->entities->page(); $this->put($page->getUrl(), [ 'name' => $page->name, 'summary' => '', @@ -647,7 +643,7 @@ class PageContentTest extends TestCase public function test_base64_images_get_extracted_from_markdown_page_content() { $this->asEditor(); - $page = Page::query()->first(); + $page = $this->entities->page(); $this->put($page->getUrl(), [ 'name' => $page->name, 'summary' => '', @@ -672,7 +668,7 @@ class PageContentTest extends TestCase $pcreRecursionLimit = ini_get('pcre.recursion_limit'); $this->asEditor(); - $page = Page::query()->first(); + $page = $this->entities->page(); ini_set('pcre.backtrack_limit', '500'); ini_set('pcre.recursion_limit', '500'); @@ -701,7 +697,7 @@ class PageContentTest extends TestCase public function test_base64_images_within_markdown_blanked_if_not_supported_extension_for_extract() { - $page = Page::query()->first(); + $page = $this->entities->page(); $this->asEditor()->put($page->getUrl(), [ 'name' => $page->name, 'summary' => '', @@ -713,7 +709,7 @@ class PageContentTest extends TestCase public function test_nested_headers_gets_assigned_an_id() { - $page = Page::query()->first(); + $page = $this->entities->page(); $content = '
Simple Test
'; $this->asEditor()->put($page->getUrl(), [ @@ -729,8 +725,7 @@ class PageContentTest extends TestCase public function test_non_breaking_spaces_are_preserved() { - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $content = '

 

'; $this->asEditor()->put($page->getUrl(), [ diff --git a/tests/Entity/PageDraftTest.php b/tests/Entity/PageDraftTest.php index 8ca73847a..acf6b01e8 100644 --- a/tests/Entity/PageDraftTest.php +++ b/tests/Entity/PageDraftTest.php @@ -85,8 +85,7 @@ class PageDraftTest extends TestCase { $admin = $this->getAdmin(); $editor = $this->getEditor(); - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $this->actingAs($editor)->put('/ajax/page/' . $page->id . '/save-draft', [ 'name' => $page->name, @@ -120,8 +119,7 @@ class PageDraftTest extends TestCase { $admin = $this->getAdmin(); $editor = $this->getEditor(); - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $this->actingAs($admin)->put('/ajax/page/' . $page->id . '/save-draft', [ 'name' => $page->name, @@ -140,8 +138,7 @@ class PageDraftTest extends TestCase public function test_draft_pages_show_on_homepage() { - /** @var Book $book */ - $book = Book::query()->first(); + $book = $this->entities->book(); $resp = $this->asAdmin()->get('/'); $this->withHtml($resp)->assertElementNotContains('#recent-drafts', 'New Page'); @@ -152,8 +149,7 @@ class PageDraftTest extends TestCase public function test_draft_pages_not_visible_by_others() { - /** @var Book $book */ - $book = Book::query()->first(); + $book = $this->entities->book(); $chapter = $book->chapters->first(); $newUser = $this->getEditor(); @@ -171,8 +167,7 @@ class PageDraftTest extends TestCase public function test_page_html_in_ajax_fetch_response() { $this->asAdmin(); - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $this->getJson('/ajax/page/' . $page->id)->assertJson([ 'html' => $page->html, @@ -181,8 +176,7 @@ class PageDraftTest extends TestCase public function test_updating_page_draft_with_markdown_retains_markdown_content() { - /** @var Book $book */ - $book = Book::query()->first(); + $book = $this->entities->book(); $this->asEditor()->get($book->getUrl('/create-page')); /** @var Page $draft */ $draft = Page::query()->where('draft', '=', true)->where('book_id', '=', $book->id)->firstOrFail(); @@ -207,8 +201,7 @@ class PageDraftTest extends TestCase public function test_slug_generated_on_draft_publish_to_page_when_no_name_change() { - /** @var Book $book */ - $book = Book::query()->first(); + $book = $this->entities->book(); $this->asEditor()->get($book->getUrl('/create-page')); /** @var Page $draft */ $draft = Page::query()->where('draft', '=', true)->where('book_id', '=', $book->id)->firstOrFail(); diff --git a/tests/Entity/PageEditorTest.php b/tests/Entity/PageEditorTest.php index e5aa549b3..6ce649a54 100644 --- a/tests/Entity/PageEditorTest.php +++ b/tests/Entity/PageEditorTest.php @@ -58,8 +58,7 @@ class PageEditorTest extends TestCase public function test_empty_markdown_still_saves_without_error() { $this->setSettings(['app-editor' => 'markdown']); - /** @var Book $book */ - $book = Book::query()->first(); + $book = $this->entities->book(); $this->asEditor()->get($book->getUrl('/create-page')); $draft = Page::query()->where('book_id', '=', $book->id) @@ -108,8 +107,7 @@ class PageEditorTest extends TestCase public function test_switching_from_html_to_clean_markdown_works() { - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $page->html = '

A Header

Some bold content.

'; $page->save(); @@ -121,8 +119,7 @@ class PageEditorTest extends TestCase public function test_switching_from_html_to_stable_markdown_works() { - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $page->html = '

A Header

Some bold content.

'; $page->save(); @@ -134,8 +131,7 @@ class PageEditorTest extends TestCase public function test_switching_from_markdown_to_wysiwyg_works() { - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $page->html = ''; $page->markdown = "## A Header\n\nSome content with **bold** text!"; $page->save(); @@ -180,8 +176,7 @@ class PageEditorTest extends TestCase public function test_page_editor_type_switch_does_not_work_without_change_editor_permissions() { - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $page->html = '

A Header

Some bold content.

'; $page->save(); @@ -193,8 +188,7 @@ class PageEditorTest extends TestCase public function test_page_save_does_not_change_active_editor_without_change_editor_permissions() { - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $page->html = '

A Header

Some bold content.

'; $page->editor = 'wysiwyg'; $page->save(); diff --git a/tests/Entity/PageRevisionTest.php b/tests/Entity/PageRevisionTest.php index 05c86c97d..eabece4c6 100644 --- a/tests/Entity/PageRevisionTest.php +++ b/tests/Entity/PageRevisionTest.php @@ -10,8 +10,7 @@ class PageRevisionTest extends TestCase { public function test_revision_links_visible_to_viewer() { - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $html = $this->withHtml($this->asViewer()->get($page->getUrl())); $html->assertLinkExists($page->getUrl('/revisions')); @@ -143,8 +142,7 @@ class PageRevisionTest extends TestCase public function test_revision_deletion() { - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $this->createRevisions($page, 2); $beforeRevisionCount = $page->revisions->count(); @@ -208,8 +206,7 @@ class PageRevisionTest extends TestCase public function test_revision_restore_action_only_visible_with_permission() { - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $this->createRevisions($page, 2); $viewer = $this->getViewer(); @@ -227,8 +224,7 @@ class PageRevisionTest extends TestCase public function test_revision_delete_action_only_visible_with_permission() { - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $this->createRevisions($page, 2); $viewer = $this->getViewer(); diff --git a/tests/Entity/PageTest.php b/tests/Entity/PageTest.php index 0f906460b..067fceeb4 100644 --- a/tests/Entity/PageTest.php +++ b/tests/Entity/PageTest.php @@ -12,8 +12,7 @@ class PageTest extends TestCase { public function test_create() { - /** @var Chapter $chapter */ - $chapter = Chapter::query()->first(); + $chapter = $this->entities->chapter(); $page = Page::factory()->make([ 'name' => 'My First Page', ]); @@ -39,7 +38,7 @@ class PageTest extends TestCase public function test_page_view_when_creator_is_deleted_but_owner_exists() { - $page = Page::query()->first(); + $page = $this->entities->page(); $user = $this->getViewer(); $owner = $this->getEditor(); $page->created_by = $user->id; @@ -55,7 +54,7 @@ class PageTest extends TestCase public function test_page_creation_with_markdown_content() { $this->setSettings(['app-editor' => 'markdown']); - $book = Book::query()->first(); + $book = $this->entities->book(); $this->asEditor()->get($book->getUrl('/create-page')); $draft = Page::query()->where('book_id', '=', $book->id) @@ -83,7 +82,7 @@ class PageTest extends TestCase public function test_page_delete() { - $page = Page::query()->first(); + $page = $this->entities->page(); $this->assertNull($page->deleted_at); $deleteViewReq = $this->asEditor()->get($page->getUrl('/delete')); @@ -103,8 +102,7 @@ class PageTest extends TestCase public function test_page_full_delete_removes_all_revisions() { - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $page->revisions()->create([ 'html' => '

ducks

', 'name' => 'my page revision', @@ -221,8 +219,7 @@ class PageTest extends TestCase public function test_old_page_slugs_redirect_to_new_pages() { - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); // Need to save twice since revisions are not generated in seeder. $this->asAdmin()->put($page->getUrl(), [ @@ -244,8 +241,7 @@ class PageTest extends TestCase public function test_page_within_chapter_deletion_returns_to_chapter() { - /** @var Chapter $chapter */ - $chapter = Chapter::query()->first(); + $chapter = $this->entities->chapter(); $page = $chapter->pages()->first(); $this->asEditor()->delete($page->getUrl()) @@ -264,8 +260,7 @@ class PageTest extends TestCase public function test_recently_updated_pages_view_shows_updated_by_details() { $user = $this->getEditor(); - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $this->actingAs($user)->put($page->getUrl(), [ 'name' => 'Updated title', diff --git a/tests/Entity/SortTest.php b/tests/Entity/SortTest.php index 93b668a0e..83a8f7005 100644 --- a/tests/Entity/SortTest.php +++ b/tests/Entity/SortTest.php @@ -33,7 +33,7 @@ class SortTest extends TestCase public function test_page_move_into_book() { - $page = Page::query()->first(); + $page = $this->entities->page(); $currentBook = $page->book; $newBook = Book::query()->where('id', '!=', $currentBook->id)->first(); @@ -55,7 +55,7 @@ class SortTest extends TestCase public function test_page_move_into_chapter() { - $page = Page::query()->first(); + $page = $this->entities->page(); $currentBook = $page->book; $newBook = Book::query()->where('id', '!=', $currentBook->id)->first(); $newChapter = $newBook->chapters()->first(); @@ -93,7 +93,7 @@ class SortTest extends TestCase public function test_page_move_requires_create_permissions_on_parent() { - $page = Page::query()->first(); + $page = $this->entities->page(); $currentBook = $page->book; $newBook = Book::query()->where('id', '!=', $currentBook->id)->first(); $editor = $this->getEditor(); @@ -118,7 +118,7 @@ class SortTest extends TestCase public function test_page_move_requires_delete_permissions() { - $page = Page::query()->first(); + $page = $this->entities->page(); $currentBook = $page->book; $newBook = Book::query()->where('id', '!=', $currentBook->id)->first(); $editor = $this->getEditor(); @@ -145,7 +145,7 @@ class SortTest extends TestCase public function test_chapter_move() { - $chapter = Chapter::query()->first(); + $chapter = $this->entities->chapter(); $currentBook = $chapter->book; $pageToCheck = $chapter->pages->first(); $newBook = Book::query()->where('id', '!=', $currentBook->id)->first(); @@ -173,7 +173,7 @@ class SortTest extends TestCase public function test_chapter_move_requires_delete_permissions() { - $chapter = Chapter::query()->first(); + $chapter = $this->entities->chapter(); $currentBook = $chapter->book; $newBook = Book::query()->where('id', '!=', $currentBook->id)->first(); $editor = $this->getEditor(); @@ -200,7 +200,7 @@ class SortTest extends TestCase public function test_chapter_move_requires_create_permissions_in_new_book() { - $chapter = Chapter::query()->first(); + $chapter = $this->entities->chapter(); $currentBook = $chapter->book; $newBook = Book::query()->where('id', '!=', $currentBook->id)->first(); $editor = $this->getEditor(); diff --git a/tests/Entity/TagTest.php b/tests/Entity/TagTest.php index d22dc2f44..18ee31826 100644 --- a/tests/Entity/TagTest.php +++ b/tests/Entity/TagTest.php @@ -102,8 +102,7 @@ class TagTest extends TestCase public function test_tags_index_shows_tag_name_as_expected_with_right_counts() { - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $page->tags()->create(['name' => 'Category', 'value' => 'GreatTestContent']); $page->tags()->create(['name' => 'Category', 'value' => 'OtherTestContent']); @@ -120,8 +119,7 @@ class TagTest extends TestCase $html->assertElementContains('a[title="Assigned to Shelves"]', '0'); $html->assertElementContains('a[href$="/tags?name=Category"]', '2 unique values'); - /** @var Book $book */ - $book = Book::query()->first(); + $book = $this->entities->book(); $book->tags()->create(['name' => 'Category', 'value' => 'GreatTestContent']); $resp = $this->asEditor()->get('/tags'); $this->withHtml($resp)->assertElementContains('a[title="Total tag usages"]', '3'); @@ -131,8 +129,7 @@ class TagTest extends TestCase public function test_tag_index_can_be_searched() { - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $page->tags()->create(['name' => 'Category', 'value' => 'GreatTestContent']); $resp = $this->asEditor()->get('/tags?search=cat'); @@ -148,8 +145,7 @@ class TagTest extends TestCase public function test_tag_index_search_will_show_mulitple_values_of_a_single_tag_name() { - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $page->tags()->create(['name' => 'Animal', 'value' => 'Catfish']); $page->tags()->create(['name' => 'Animal', 'value' => 'Catdog']); @@ -160,8 +156,7 @@ class TagTest extends TestCase public function test_tag_index_can_be_scoped_to_specific_tag_name() { - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $page->tags()->create(['name' => 'Category', 'value' => 'GreatTestContent']); $page->tags()->create(['name' => 'Category', 'value' => 'OtherTestContent']); $page->tags()->create(['name' => 'OtherTagName', 'value' => 'OtherValue']); @@ -178,8 +173,7 @@ class TagTest extends TestCase public function test_tags_index_adheres_to_page_permissions() { - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $page->tags()->create(['name' => 'SuperCategory', 'value' => 'GreatTestContent']); $resp = $this->asEditor()->get('/tags'); @@ -216,7 +210,7 @@ class TagTest extends TestCase public function test_tag_classes_are_escaped() { - $page = Page::query()->first(); + $page = $this->entities->page(); $page->tags()->create(['name' => '<>']); $resp = $this->asEditor()->get($page->getUrl()); $resp->assertDontSee('tag-name-<>', false); diff --git a/tests/ErrorTest.php b/tests/ErrorTest.php index 2eeb6537e..c46d65bde 100644 --- a/tests/ErrorTest.php +++ b/tests/ErrorTest.php @@ -27,7 +27,7 @@ class ErrorTest extends TestCase { $this->actingAs($this->getViewer()); $handler = $this->withTestLogger(); - $book = Book::query()->first(); + $book = $this->entities->book(); // Ensure we're seeing errors Log::error('cat'); diff --git a/tests/FavouriteTest.php b/tests/FavouriteTest.php index de36c77e1..03a712316 100644 --- a/tests/FavouriteTest.php +++ b/tests/FavouriteTest.php @@ -13,7 +13,7 @@ class FavouriteTest extends TestCase { public function test_page_add_favourite_flow() { - $page = Page::query()->first(); + $page = $this->entities->page(); $editor = $this->getEditor(); $resp = $this->actingAs($editor)->get($page->getUrl()); @@ -36,7 +36,7 @@ class FavouriteTest extends TestCase public function test_page_remove_favourite_flow() { - $page = Page::query()->first(); + $page = $this->entities->page(); $editor = $this->getEditor(); Favourite::query()->forceCreate([ 'user_id' => $editor->id, @@ -62,8 +62,7 @@ class FavouriteTest extends TestCase public function test_favourite_flow_with_own_permissions() { - /** @var Book $book */ - $book = Book::query()->first(); + $book = $this->entities->book(); $user = User::factory()->create(); $book->owned_by = $user->id; $book->save(); @@ -115,8 +114,7 @@ class FavouriteTest extends TestCase $resp = $this->actingAs($editor)->get('/'); $this->withHtml($resp)->assertElementNotExists('#top-favourites'); - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $page->favourites()->save((new Favourite())->forceFill(['user_id' => $editor->id])); $resp = $this->get('/'); @@ -126,8 +124,7 @@ class FavouriteTest extends TestCase public function test_favourites_list_page_shows_favourites_and_has_working_pagination() { - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $editor = $this->getEditor(); $resp = $this->actingAs($editor)->get('/favourites'); diff --git a/tests/Helpers/EntityProvider.php b/tests/Helpers/EntityProvider.php index d3888e71f..152f7a3ac 100644 --- a/tests/Helpers/EntityProvider.php +++ b/tests/Helpers/EntityProvider.php @@ -141,7 +141,7 @@ class EntityProvider */ public function newPage(array $input = ['name' => 'test page', 'html' => 'My new test page']): Page { - $book = Book::query()->first(); + $book = $this->book(); $pageRepo = app(PageRepo::class); $draftPage = $pageRepo->getNewDraftPage($book); $this->addToCache($draftPage); diff --git a/tests/HomepageTest.php b/tests/HomepageTest.php index bb42f49f2..60e10a087 100644 --- a/tests/HomepageTest.php +++ b/tests/HomepageTest.php @@ -81,8 +81,7 @@ class HomepageTest extends TestCase public function test_custom_homepage_cannot_be_deleted_from_parent_deletion() { - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $this->setSettings([ 'app-homepage' => $page->id, 'app-homepage-type' => 'page', @@ -161,7 +160,7 @@ class HomepageTest extends TestCase $this->setSettings(['app-homepage-type' => 'bookshelves']); $this->asEditor(); - $shelf = Bookshelf::query()->first(); + $shelf = $this->entities->shelf(); $book = $shelf->books()->first(); // Ensure initially visible diff --git a/tests/OpenGraphTest.php b/tests/OpenGraphTest.php index 3f807f024..f3c439767 100644 --- a/tests/OpenGraphTest.php +++ b/tests/OpenGraphTest.php @@ -18,7 +18,7 @@ class OpenGraphTest extends TestCase public function test_page_tags() { - $page = Page::query()->first(); + $page = $this->entities->page(); $resp = $this->asEditor()->get($page->getUrl()); $tags = $this->getOpenGraphTags($resp); @@ -29,7 +29,7 @@ class OpenGraphTest extends TestCase public function test_chapter_tags() { - $chapter = Chapter::query()->first(); + $chapter = $this->entities->chapter(); $resp = $this->asEditor()->get($chapter->getUrl()); $tags = $this->getOpenGraphTags($resp); @@ -40,7 +40,7 @@ class OpenGraphTest extends TestCase public function test_book_tags() { - $book = Book::query()->first(); + $book = $this->entities->book(); $resp = $this->asEditor()->get($book->getUrl()); $tags = $this->getOpenGraphTags($resp); @@ -60,7 +60,7 @@ class OpenGraphTest extends TestCase public function test_shelf_tags() { - $shelf = Bookshelf::query()->first(); + $shelf = $this->entities->shelf(); $resp = $this->asEditor()->get($shelf->getUrl()); $tags = $this->getOpenGraphTags($resp); diff --git a/tests/Permissions/EntityOwnerChangeTest.php b/tests/Permissions/EntityOwnerChangeTest.php index fe508668e..65a67dc0f 100644 --- a/tests/Permissions/EntityOwnerChangeTest.php +++ b/tests/Permissions/EntityOwnerChangeTest.php @@ -13,7 +13,7 @@ class EntityOwnerChangeTest extends TestCase { public function test_changing_page_owner() { - $page = Page::query()->first(); + $page = $this->entities->page(); $user = User::query()->where('id', '!=', $page->owned_by)->first(); $this->asAdmin()->put($page->getUrl('permissions'), ['owned_by' => $user->id]); @@ -22,7 +22,7 @@ class EntityOwnerChangeTest extends TestCase public function test_changing_chapter_owner() { - $chapter = Chapter::query()->first(); + $chapter = $this->entities->chapter(); $user = User::query()->where('id', '!=', $chapter->owned_by)->first(); $this->asAdmin()->put($chapter->getUrl('permissions'), ['owned_by' => $user->id]); @@ -31,7 +31,7 @@ class EntityOwnerChangeTest extends TestCase public function test_changing_book_owner() { - $book = Book::query()->first(); + $book = $this->entities->book(); $user = User::query()->where('id', '!=', $book->owned_by)->first(); $this->asAdmin()->put($book->getUrl('permissions'), ['owned_by' => $user->id]); @@ -40,7 +40,7 @@ class EntityOwnerChangeTest extends TestCase public function test_changing_shelf_owner() { - $shelf = Bookshelf::query()->first(); + $shelf = $this->entities->shelf(); $user = User::query()->where('id', '!=', $shelf->owned_by)->first(); $this->asAdmin()->put($shelf->getUrl('permissions'), ['owned_by' => $user->id]); diff --git a/tests/Permissions/EntityPermissionsTest.php b/tests/Permissions/EntityPermissionsTest.php index 9e80b752a..9312b88cf 100644 --- a/tests/Permissions/EntityPermissionsTest.php +++ b/tests/Permissions/EntityPermissionsTest.php @@ -41,8 +41,7 @@ class EntityPermissionsTest extends TestCase public function test_bookshelf_view_restriction() { - /** @var Bookshelf $shelf */ - $shelf = Bookshelf::query()->first(); + $shelf = $this->entities->shelf(); $this->actingAs($this->user) ->get($shelf->getUrl()) @@ -61,8 +60,7 @@ class EntityPermissionsTest extends TestCase public function test_bookshelf_update_restriction() { - /** @var Bookshelf $shelf */ - $shelf = Bookshelf::query()->first(); + $shelf = $this->entities->shelf(); $this->actingAs($this->user) ->get($shelf->getUrl('/edit')) @@ -82,8 +80,7 @@ class EntityPermissionsTest extends TestCase public function test_bookshelf_delete_restriction() { - /** @var Bookshelf $shelf */ - $shelf = Bookshelf::query()->first(); + $shelf = $this->entities->shelf(); $this->actingAs($this->user) ->get($shelf->getUrl('/delete')) @@ -103,8 +100,7 @@ class EntityPermissionsTest extends TestCase public function test_book_view_restriction() { - /** @var Book $book */ - $book = Book::query()->first(); + $book = $this->entities->book(); $bookPage = $book->pages->first(); $bookChapter = $book->chapters->first(); @@ -134,8 +130,7 @@ class EntityPermissionsTest extends TestCase public function test_book_create_restriction() { - /** @var Book $book */ - $book = Book::query()->first(); + $book = $this->entities->book(); $bookUrl = $book->getUrl(); $resp = $this->actingAs($this->viewer)->get($bookUrl); @@ -181,8 +176,7 @@ class EntityPermissionsTest extends TestCase public function test_book_update_restriction() { - /** @var Book $book */ - $book = Book::query()->first(); + $book = $this->entities->book(); $bookPage = $book->pages->first(); $bookChapter = $book->chapters->first(); @@ -209,8 +203,7 @@ class EntityPermissionsTest extends TestCase public function test_book_delete_restriction() { - /** @var Book $book */ - $book = Book::query()->first(); + $book = $this->entities->book(); $bookPage = $book->pages->first(); $bookChapter = $book->chapters->first(); @@ -236,8 +229,7 @@ class EntityPermissionsTest extends TestCase public function test_chapter_view_restriction() { - /** @var Chapter $chapter */ - $chapter = Chapter::query()->first(); + $chapter = $this->entities->chapter(); $chapterPage = $chapter->pages->first(); $chapterUrl = $chapter->getUrl(); @@ -256,8 +248,7 @@ class EntityPermissionsTest extends TestCase public function test_chapter_create_restriction() { - /** @var Chapter $chapter */ - $chapter = Chapter::query()->first(); + $chapter = $this->entities->chapter(); $chapterUrl = $chapter->getUrl(); $resp = $this->actingAs($this->user)->get($chapterUrl); @@ -285,8 +276,7 @@ class EntityPermissionsTest extends TestCase public function test_chapter_update_restriction() { - /** @var Chapter $chapter */ - $chapter = Chapter::query()->first(); + $chapter = $this->entities->chapter(); $chapterPage = $chapter->pages->first(); $chapterUrl = $chapter->getUrl(); @@ -308,8 +298,7 @@ class EntityPermissionsTest extends TestCase public function test_chapter_delete_restriction() { - /** @var Chapter $chapter */ - $chapter = Chapter::query()->first(); + $chapter = $this->entities->chapter(); $chapterPage = $chapter->pages->first(); $chapterUrl = $chapter->getUrl(); @@ -332,8 +321,7 @@ class EntityPermissionsTest extends TestCase public function test_page_view_restriction() { - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $pageUrl = $page->getUrl(); $this->actingAs($this->user)->get($pageUrl)->assertOk(); @@ -349,8 +337,7 @@ class EntityPermissionsTest extends TestCase public function test_page_update_restriction() { - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $pageUrl = $page->getUrl(); $resp = $this->actingAs($this->user) @@ -371,8 +358,7 @@ class EntityPermissionsTest extends TestCase public function test_page_delete_restriction() { - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $pageUrl = $page->getUrl(); $this->actingAs($this->user) @@ -436,8 +422,7 @@ class EntityPermissionsTest extends TestCase public function test_restricted_pages_not_visible_in_book_navigation_on_pages() { - /** @var Chapter $chapter */ - $chapter = Chapter::query()->first(); + $chapter = $this->entities->chapter(); $page = $chapter->pages->first(); $page2 = $chapter->pages[2]; @@ -449,8 +434,7 @@ class EntityPermissionsTest extends TestCase public function test_restricted_pages_not_visible_in_book_navigation_on_chapters() { - /** @var Chapter $chapter */ - $chapter = Chapter::query()->first(); + $chapter = $this->entities->chapter(); $page = $chapter->pages->first(); $this->setRestrictionsForTestRoles($page, []); @@ -461,8 +445,7 @@ class EntityPermissionsTest extends TestCase public function test_restricted_pages_not_visible_on_chapter_pages() { - /** @var Chapter $chapter */ - $chapter = Chapter::query()->first(); + $chapter = $this->entities->chapter(); $page = $chapter->pages->first(); $this->setRestrictionsForTestRoles($page, []); @@ -474,8 +457,7 @@ class EntityPermissionsTest extends TestCase public function test_restricted_chapter_pages_not_visible_on_book_page() { - /** @var Chapter $chapter */ - $chapter = Chapter::query()->first(); + $chapter = $this->entities->chapter(); $this->actingAs($this->user) ->get($chapter->book->getUrl()) ->assertSee($chapter->pages->first()->name); @@ -491,8 +473,7 @@ class EntityPermissionsTest extends TestCase public function test_bookshelf_update_restriction_override() { - /** @var Bookshelf $shelf */ - $shelf = Bookshelf::query()->first(); + $shelf = $this->entities->shelf(); $this->actingAs($this->viewer) ->get($shelf->getUrl('/edit')) @@ -510,8 +491,7 @@ class EntityPermissionsTest extends TestCase public function test_bookshelf_delete_restriction_override() { - /** @var Bookshelf $shelf */ - $shelf = Bookshelf::query()->first(); + $shelf = $this->entities->shelf(); $this->actingAs($this->viewer) ->get($shelf->getUrl('/delete')) @@ -529,8 +509,7 @@ class EntityPermissionsTest extends TestCase public function test_book_create_restriction_override() { - /** @var Book $book */ - $book = Book::query()->first(); + $book = $this->entities->book(); $bookUrl = $book->getUrl(); $resp = $this->actingAs($this->viewer)->get($bookUrl); @@ -571,8 +550,7 @@ class EntityPermissionsTest extends TestCase public function test_book_update_restriction_override() { - /** @var Book $book */ - $book = Book::query()->first(); + $book = $this->entities->book(); $bookPage = $book->pages->first(); $bookChapter = $book->chapters->first(); @@ -598,8 +576,7 @@ class EntityPermissionsTest extends TestCase public function test_book_delete_restriction_override() { - /** @var Book $book */ - $book = Book::query()->first(); + $book = $this->entities->book(); $bookPage = $book->pages->first(); $bookChapter = $book->chapters->first(); @@ -626,8 +603,7 @@ class EntityPermissionsTest extends TestCase public function test_page_visible_if_has_permissions_when_book_not_visible() { - /** @var Book $book */ - $book = Book::query()->first(); + $book = $this->entities->book(); $bookChapter = $book->chapters->first(); $bookPage = $bookChapter->pages->first(); @@ -667,8 +643,7 @@ class EntityPermissionsTest extends TestCase public function test_can_create_page_if_chapter_has_permissions_when_book_not_visible() { - /** @var Book $book */ - $book = Book::query()->first(); + $book = $this->entities->book(); $this->setRestrictionsForTestRoles($book, []); $bookChapter = $book->chapters->first(); $this->setRestrictionsForTestRoles($bookChapter, ['view']); diff --git a/tests/Permissions/ExportPermissionsTest.php b/tests/Permissions/ExportPermissionsTest.php index 7e9ce6100..44f1a35cc 100644 --- a/tests/Permissions/ExportPermissionsTest.php +++ b/tests/Permissions/ExportPermissionsTest.php @@ -11,7 +11,7 @@ class ExportPermissionsTest extends TestCase { public function test_page_content_without_view_access_hidden_on_chapter_export() { - $chapter = Chapter::query()->first(); + $chapter = $this->entities->chapter(); $page = $chapter->pages()->firstOrFail(); $pageContent = Str::random(48); $page->html = '

' . $pageContent . '

'; @@ -39,7 +39,7 @@ class ExportPermissionsTest extends TestCase public function test_page_content_without_view_access_hidden_on_book_export() { - $book = Book::query()->first(); + $book = $this->entities->book(); $page = $book->pages()->firstOrFail(); $pageContent = Str::random(48); $page->html = '

' . $pageContent . '

'; diff --git a/tests/Permissions/RolesTest.php b/tests/Permissions/RolesTest.php index a24d5f8d8..23bfde74c 100644 --- a/tests/Permissions/RolesTest.php +++ b/tests/Permissions/RolesTest.php @@ -520,8 +520,7 @@ class RolesTest extends TestCase public function test_chapter_create_all_permissions() { - /** @var Book $book */ - $book = Book::query()->first(); + $book = $this->entities->book(); $this->checkAccessPermission('chapter-create-all', [ $book->getUrl('/create-chapter'), ], [ @@ -603,10 +602,8 @@ class RolesTest extends TestCase public function test_page_create_own_permissions() { - /** @var Book $book */ - $book = Book::query()->first(); - /** @var Chapter $chapter */ - $chapter = Chapter::query()->first(); + $book = $this->entities->book(); + $chapter = $this->entities->chapter(); $entities = $this->entities->createChainBelongingToUser($this->user); $ownBook = $entities['book']; @@ -652,10 +649,8 @@ class RolesTest extends TestCase public function test_page_create_all_permissions() { - /** @var Book $book */ - $book = Book::query()->first(); - /** @var Chapter $chapter */ - $chapter = Chapter::query()->first(); + $book = $this->entities->book(); + $chapter = $this->entities->chapter(); $createUrl = $book->getUrl('/create-page'); $createUrlChapter = $chapter->getUrl('/create-page'); @@ -806,8 +801,7 @@ class RolesTest extends TestCase public function test_image_delete_own_permission() { $this->giveUserPermissions($this->user, ['image-update-all']); - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $image = Image::factory()->create([ 'uploaded_to' => $page->id, 'created_by' => $this->user->id, @@ -826,8 +820,7 @@ class RolesTest extends TestCase { $this->giveUserPermissions($this->user, ['image-update-all']); $admin = $this->getAdmin(); - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $image = Image::factory()->create(['uploaded_to' => $page->id, 'created_by' => $admin->id, 'updated_by' => $admin->id]); $this->actingAs($this->user)->json('delete', '/images/' . $image->id)->assertStatus(403); @@ -845,8 +838,7 @@ class RolesTest extends TestCase public function test_role_permission_removal() { // To cover issue fixed in f99c8ff99aee9beb8c692f36d4b84dc6e651e50a. - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $viewerRole = Role::getRole('viewer'); $viewer = $this->getViewer(); $this->actingAs($viewer)->get($page->getUrl())->assertOk(); diff --git a/tests/PublicActionTest.php b/tests/PublicActionTest.php index 309e09600..14759c578 100644 --- a/tests/PublicActionTest.php +++ b/tests/PublicActionTest.php @@ -17,11 +17,11 @@ class PublicActionTest extends TestCase public function test_app_not_public() { $this->setSettings(['app-public' => 'false']); - $book = Book::query()->first(); + $book = $this->entities->book(); $this->get('/books')->assertRedirect('/login'); $this->get($book->getUrl())->assertRedirect('/login'); - $page = Page::query()->first(); + $page = $this->entities->page(); $this->get($page->getUrl())->assertRedirect('/login'); } @@ -93,8 +93,7 @@ class PublicActionTest extends TestCase $this->app->make(JointPermissionBuilder::class)->rebuildForRole($publicRole); user()->clearPermissionCache(); - /** @var Chapter $chapter */ - $chapter = Chapter::query()->first(); + $chapter = $this->entities->chapter(); $resp = $this->get($chapter->getUrl()); $resp->assertSee('New Page'); $this->withHtml($resp)->assertElementExists('a[href="' . $chapter->getUrl('/create-page') . '"]'); @@ -118,7 +117,7 @@ class PublicActionTest extends TestCase public function test_content_not_listed_on_404_for_public_users() { - $page = Page::query()->first(); + $page = $this->entities->page(); $page->fill(['name' => 'my testing random unique page name'])->save(); $this->asAdmin()->get($page->getUrl()); // Fake visit to show on recents $resp = $this->get('/cats/dogs/hippos'); @@ -162,8 +161,7 @@ class PublicActionTest extends TestCase public function test_public_view_then_login_redirects_to_previous_content() { $this->setSettings(['app-public' => 'true']); - /** @var Book $book */ - $book = Book::query()->first(); + $book = $this->entities->book(); $resp = $this->get($book->getUrl()); $resp->assertSee($book->name); @@ -175,8 +173,7 @@ class PublicActionTest extends TestCase public function test_access_hidden_content_then_login_redirects_to_intended_content() { $this->setSettings(['app-public' => 'true']); - /** @var Book $book */ - $book = Book::query()->first(); + $book = $this->entities->book(); $this->entities->setPermissions($book); $resp = $this->get($book->getUrl()); diff --git a/tests/References/CrossLinkParserTest.php b/tests/References/CrossLinkParserTest.php index 43b8a36ae..eb862a9fd 100644 --- a/tests/References/CrossLinkParserTest.php +++ b/tests/References/CrossLinkParserTest.php @@ -40,7 +40,7 @@ class CrossLinkParserTest extends TestCase public function test_similar_page_and_book_reference_links_dont_conflict() { - $page = Page::query()->first(); + $page = $this->entities->page(); $book = $page->book; $html = ' diff --git a/tests/References/ReferencesTest.php b/tests/References/ReferencesTest.php index 59263ee0c..16ea19ac5 100644 --- a/tests/References/ReferencesTest.php +++ b/tests/References/ReferencesTest.php @@ -97,7 +97,7 @@ class ReferencesTest extends TestCase { /** @var Page $page */ /** @var Page $pageB */ - $page = Page::query()->first(); + $page = $this->entities->page(); $pageB = Page::query()->where('id', '!=', $page->id)->first(); $this->createReference($pageB, $page); @@ -109,8 +109,7 @@ class ReferencesTest extends TestCase public function test_reference_page_shows_empty_state_with_no_references() { - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $this->asEditor() ->get($page->getUrl('/references')) @@ -124,7 +123,7 @@ class ReferencesTest extends TestCase /** @var Book $book */ $pageA = Page::query()->first(); $pageB = Page::query()->where('id', '!=', $pageA->id)->first(); - $book = Book::query()->first(); + $book = $this->entities->book(); foreach ([$pageA, $pageB] as $page) { $page->html = 'Link'; @@ -200,8 +199,8 @@ class ReferencesTest extends TestCase { /** @var Page $page */ /** @var Book $book */ - $page = Page::query()->first(); - $book = Book::query()->first(); + $page = $this->entities->page(); + $book = $this->entities->book(); $bookUrl = $book->getUrl(); $markdown = ' diff --git a/tests/Settings/RecycleBinTest.php b/tests/Settings/RecycleBinTest.php index 465c1aaad..8b5705afd 100644 --- a/tests/Settings/RecycleBinTest.php +++ b/tests/Settings/RecycleBinTest.php @@ -16,7 +16,7 @@ class RecycleBinTest extends TestCase { public function test_recycle_bin_routes_permissions() { - $page = Page::query()->first(); + $page = $this->entities->page(); $editor = $this->getEditor(); $this->actingAs($editor)->delete($page->getUrl()); $deletion = Deletion::query()->firstOrFail(); @@ -57,7 +57,7 @@ class RecycleBinTest extends TestCase public function test_recycle_bin_view() { - $page = Page::query()->first(); + $page = $this->entities->page(); $book = Book::query()->whereHas('pages')->whereHas('chapters')->withCount(['pages', 'chapters'])->first(); $editor = $this->getEditor(); $this->actingAs($editor)->delete($page->getUrl()); @@ -74,7 +74,7 @@ class RecycleBinTest extends TestCase public function test_recycle_bin_empty() { - $page = Page::query()->first(); + $page = $this->entities->page(); $book = Book::query()->where('id', '!=', $page->book_id)->whereHas('pages')->whereHas('chapters')->with(['pages', 'chapters'])->firstOrFail(); $editor = $this->getEditor(); $this->actingAs($editor)->delete($page->getUrl()); diff --git a/tests/ThemeTest.php b/tests/ThemeTest.php index ac4b35de2..4d612a870 100644 --- a/tests/ThemeTest.php +++ b/tests/ThemeTest.php @@ -64,7 +64,7 @@ class ThemeTest extends TestCase }; Theme::listen(ThemeEvents::COMMONMARK_ENVIRONMENT_CONFIGURE, $callback); - $page = Page::query()->first(); + $page = $this->entities->page(); $content = new PageContent($page); $content->setNewMarkdown('# test'); @@ -199,7 +199,7 @@ class ThemeTest extends TestCase public function test_event_activity_logged() { - $book = Book::query()->first(); + $book = $this->entities->book(); $args = []; $callback = function (...$eventArgs) use (&$args) { $args = $eventArgs; @@ -218,7 +218,7 @@ class ThemeTest extends TestCase { /** @var Page $page */ /** @var Page $otherPage */ - $page = Page::query()->first(); + $page = $this->entities->page(); $otherPage = Page::query()->where('id', '!=', $page->id)->first(); $otherPage->html = '

This is a really cool section

'; $page->html = "

{{@{$otherPage->id}#bkmrk-cool}}

"; @@ -324,8 +324,7 @@ class ThemeTest extends TestCase { $bodyStartStr = 'garry-fought-against-the-panther'; $bodyEndStr = 'garry-lost-his-fight-with-grace'; - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $this->usingThemeFolder(function (string $folder) use ($bodyStartStr, $bodyEndStr, $page) { $viewDir = theme_path('layouts/parts'); diff --git a/tests/Uploads/AttachmentTest.php b/tests/Uploads/AttachmentTest.php index 7280510f3..915a9ba4d 100644 --- a/tests/Uploads/AttachmentTest.php +++ b/tests/Uploads/AttachmentTest.php @@ -73,7 +73,7 @@ class AttachmentTest extends TestCase public function test_file_upload() { - $page = Page::query()->first(); + $page = $this->entities->page(); $this->asAdmin(); $admin = $this->getAdmin(); $fileName = 'upload_test_file.txt'; @@ -101,7 +101,7 @@ class AttachmentTest extends TestCase public function test_file_upload_does_not_use_filename() { - $page = Page::query()->first(); + $page = $this->entities->page(); $fileName = 'upload_test_file.txt'; $upload = $this->asAdmin()->uploadFile($fileName, $page->id); @@ -115,7 +115,7 @@ class AttachmentTest extends TestCase public function test_file_display_and_access() { - $page = Page::query()->first(); + $page = $this->entities->page(); $this->asAdmin(); $fileName = 'upload_test_file.txt'; @@ -136,7 +136,7 @@ class AttachmentTest extends TestCase public function test_attaching_link_to_page() { - $page = Page::query()->first(); + $page = $this->entities->page(); $admin = $this->getAdmin(); $this->asAdmin(); @@ -173,7 +173,7 @@ class AttachmentTest extends TestCase public function test_attachment_updating() { - $page = Page::query()->first(); + $page = $this->entities->page(); $this->asAdmin(); $attachment = $this->createAttachment($page); @@ -197,7 +197,7 @@ class AttachmentTest extends TestCase public function test_file_deletion() { - $page = Page::query()->first(); + $page = $this->entities->page(); $this->asAdmin(); $fileName = 'deletion_test.txt'; $this->uploadFile($fileName, $page->id); @@ -219,7 +219,7 @@ class AttachmentTest extends TestCase public function test_attachment_deletion_on_page_deletion() { - $page = Page::query()->first(); + $page = $this->entities->page(); $this->asAdmin(); $fileName = 'deletion_test.txt'; $this->uploadFile($fileName, $page->id); @@ -247,7 +247,7 @@ class AttachmentTest extends TestCase { $admin = $this->getAdmin(); $viewer = $this->getViewer(); - $page = Page::query()->first(); /** @var Page $page */ + $page = $this->entities->page(); /** @var Page $page */ $this->actingAs($admin); $fileName = 'permission_test.txt'; $this->uploadFile($fileName, $page->id); @@ -269,7 +269,7 @@ class AttachmentTest extends TestCase public function test_data_and_js_links_cannot_be_attached_to_a_page() { - $page = Page::query()->first(); + $page = $this->entities->page(); $this->asAdmin(); $badLinks = [ @@ -310,7 +310,7 @@ class AttachmentTest extends TestCase public function test_file_access_with_open_query_param_provides_inline_response_with_correct_content_type() { - $page = Page::query()->first(); + $page = $this->entities->page(); $this->asAdmin(); $fileName = 'upload_test_file.txt'; @@ -329,7 +329,7 @@ class AttachmentTest extends TestCase public function test_html_file_access_with_open_forces_plain_content_type() { - $page = Page::query()->first(); + $page = $this->entities->page(); $this->asAdmin(); $attachment = $this->createUploadAttachment($page, 'test_file.html', '

testing

', 'text/html'); @@ -346,7 +346,7 @@ class AttachmentTest extends TestCase { config()->set('filesystems.attachments', 'local_secure_restricted'); - $page = Page::query()->first(); + $page = $this->entities->page(); $fileName = 'upload_test_file.txt'; $upload = $this->asAdmin()->uploadFile($fileName, $page->id); diff --git a/tests/Uploads/ImageTest.php b/tests/Uploads/ImageTest.php index 184da214c..e929d63ec 100644 --- a/tests/Uploads/ImageTest.php +++ b/tests/Uploads/ImageTest.php @@ -15,7 +15,7 @@ class ImageTest extends TestCase public function test_image_upload() { - $page = Page::query()->first(); + $page = $this->entities->page(); $admin = $this->getAdmin(); $this->actingAs($admin); @@ -39,7 +39,7 @@ class ImageTest extends TestCase public function test_image_display_thumbnail_generation_does_not_increase_image_size() { - $page = Page::query()->first(); + $page = $this->entities->page(); $admin = $this->getAdmin(); $this->actingAs($admin); @@ -63,7 +63,7 @@ class ImageTest extends TestCase public function test_image_display_thumbnail_generation_for_apng_images_uses_original_file() { - $page = Page::query()->first(); + $page = $this->entities->page(); $admin = $this->getAdmin(); $this->actingAs($admin); @@ -125,7 +125,7 @@ class ImageTest extends TestCase public function test_image_usage() { - $page = Page::query()->first(); + $page = $this->entities->page(); $editor = $this->getEditor(); $this->actingAs($editor); @@ -145,7 +145,7 @@ class ImageTest extends TestCase public function test_php_files_cannot_be_uploaded() { - $page = Page::query()->first(); + $page = $this->entities->page(); $admin = $this->getAdmin(); $this->actingAs($admin); @@ -167,7 +167,7 @@ class ImageTest extends TestCase public function test_php_like_files_cannot_be_uploaded() { - $page = Page::query()->first(); + $page = $this->entities->page(); $admin = $this->getAdmin(); $this->actingAs($admin); @@ -184,7 +184,7 @@ class ImageTest extends TestCase public function test_files_with_double_extensions_will_get_sanitized() { - $page = Page::query()->first(); + $page = $this->entities->page(); $admin = $this->getAdmin(); $this->actingAs($admin); @@ -219,7 +219,7 @@ class ImageTest extends TestCase ]; foreach ($badNames as $name) { $galleryFile = $this->getTestImage($name); - $page = Page::query()->first(); + $page = $this->entities->page(); $badPath = $this->getTestImagePath('gallery', $name); $this->deleteImage($badPath); @@ -244,7 +244,7 @@ class ImageTest extends TestCase config()->set('filesystems.images', 'local_secure'); $this->asEditor(); $galleryFile = $this->getTestImage('my-secure-test-upload.png'); - $page = Page::query()->first(); + $page = $this->entities->page(); $expectedPath = storage_path('uploads/images/gallery/' . date('Y-m') . '/my-secure-test-upload.png'); $upload = $this->call('POST', '/images/gallery', ['uploaded_to' => $page->id], [], ['file' => $galleryFile], []); @@ -292,7 +292,7 @@ class ImageTest extends TestCase config()->set('filesystems.images', 'local_secure'); $this->asEditor(); $galleryFile = $this->getTestImage('my-secure-test-upload.png'); - $page = Page::query()->first(); + $page = $this->entities->page(); $expectedPath = storage_path('uploads/images/gallery/' . date('Y-m') . '/my-secure-test-upload.png'); $upload = $this->call('POST', '/images/gallery', ['uploaded_to' => $page->id], [], ['file' => $galleryFile], []); @@ -332,8 +332,7 @@ class ImageTest extends TestCase config()->set('filesystems.images', 'local_secure_restricted'); $this->asEditor(); $galleryFile = $this->getTestImage('my-secure-restricted-test-upload.png'); - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $upload = $this->call('POST', '/images/gallery', ['uploaded_to' => $page->id], [], ['file' => $galleryFile], []); $upload->assertStatus(200); @@ -357,8 +356,7 @@ class ImageTest extends TestCase config()->set('filesystems.images', 'local_secure_restricted'); $this->asEditor(); $galleryFile = $this->getTestImage('my-secure-restricted-thumb-test-test.png'); - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $upload = $this->call('POST', '/images/gallery', ['uploaded_to' => $page->id], [], ['file' => $galleryFile], []); $upload->assertStatus(200); @@ -412,7 +410,7 @@ class ImageTest extends TestCase public function test_image_delete() { - $page = Page::query()->first(); + $page = $this->entities->page(); $this->asAdmin(); $imageName = 'first-image.png'; $relPath = $this->getTestImagePath('gallery', $imageName); @@ -434,7 +432,7 @@ class ImageTest extends TestCase public function test_image_delete_does_not_delete_similar_images() { - $page = Page::query()->first(); + $page = $this->entities->page(); $this->asAdmin(); $imageName = 'first-image.png'; @@ -459,7 +457,7 @@ class ImageTest extends TestCase public function test_image_manager_delete_button_only_shows_with_permission() { - $page = Page::query()->first(); + $page = $this->entities->page(); $this->asAdmin(); $imageName = 'first-image.png'; $relPath = $this->getTestImagePath('gallery', $imageName); @@ -539,7 +537,7 @@ class ImageTest extends TestCase public function test_deleted_unused_images() { - $page = Page::query()->first(); + $page = $this->entities->page(); $admin = $this->getAdmin(); $this->actingAs($admin); diff --git a/tests/Uploads/UsesImages.php b/tests/Uploads/UsesImages.php index b55572248..e2c16c37c 100644 --- a/tests/Uploads/UsesImages.php +++ b/tests/Uploads/UsesImages.php @@ -91,7 +91,7 @@ trait UsesImages protected function uploadGalleryImage(Page $page = null, ?string $testDataFileName = null) { if ($page === null) { - $page = Page::query()->first(); + $page = $this->entities->page(); } $imageName = $testDataFileName ?? 'first-image.png'; diff --git a/tests/User/UserManagementTest.php b/tests/User/UserManagementTest.php index 71d50e8d6..114088338 100644 --- a/tests/User/UserManagementTest.php +++ b/tests/User/UserManagementTest.php @@ -150,7 +150,7 @@ class UserManagementTest extends TestCase public function test_delete_with_new_owner_id_changes_ownership() { - $page = Page::query()->first(); + $page = $this->entities->page(); $owner = $page->ownedBy; $newOwner = User::query()->where('id', '!=', $owner->id)->first(); diff --git a/tests/User/UserPreferencesTest.php b/tests/User/UserPreferencesTest.php index 88d54d316..2273be2b4 100644 --- a/tests/User/UserPreferencesTest.php +++ b/tests/User/UserPreferencesTest.php @@ -132,8 +132,7 @@ class UserPreferencesTest extends TestCase public function test_shelf_view_type_change() { $editor = $this->getEditor(); - /** @var Bookshelf $shelf */ - $shelf = Bookshelf::query()->first(); + $shelf = $this->entities->shelf(); setting()->putUser($editor, 'bookshelf_view_type', 'list'); $resp = $this->actingAs($editor)->get($shelf->getUrl())->assertSee('Grid View'); @@ -155,7 +154,7 @@ class UserPreferencesTest extends TestCase public function test_update_code_language_favourite() { $editor = $this->getEditor(); - $page = Page::query()->first(); + $page = $this->entities->page(); $this->actingAs($editor); $this->patch('/settings/users/update-code-language-favourite', ['language' => 'php', 'active' => true]); From 900e853b1568cf24dea52b09aa0fa6582e670645 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 29 Sep 2022 22:11:16 +0100 Subject: [PATCH 22/55] Quick run through of applying new test entity helper class --- app/Api/ListingResponseBuilder.php | 1 - tests/Actions/AuditLogTest.php | 5 +- tests/Actions/WebhookCallTest.php | 1 - tests/Actions/WebhookFormatTesting.php | 9 ++-- tests/Api/BooksApiTest.php | 21 ++++---- tests/Api/ChaptersApiTest.php | 15 +++--- tests/Api/PagesApiTest.php | 33 ++++++------ tests/Api/RecycleBinApiTest.php | 3 +- tests/Auth/AuthTest.php | 1 - tests/Commands/ClearActivityCommandTest.php | 1 - .../CopyShelfPermissionsCommandTest.php | 2 +- .../RegenerateReferencesCommandTest.php | 1 - tests/Commands/UpdateUrlCommandTest.php | 1 - tests/Entity/BookShelfTest.php | 16 +++--- tests/Entity/ChapterTest.php | 6 +-- tests/Entity/CommentSettingTest.php | 15 ++---- tests/Entity/CommentTest.php | 10 ++-- tests/Entity/ConvertTest.php | 3 +- tests/Entity/EntityAccessTest.php | 5 +- tests/Entity/EntitySearchTest.php | 20 ++++--- tests/Entity/ExportTest.php | 12 ++--- tests/Entity/PageContentTest.php | 4 +- tests/Entity/PageDraftTest.php | 14 ++--- tests/Entity/PageEditorTest.php | 9 ++-- tests/Entity/PageRevisionTest.php | 21 ++++---- tests/Entity/PageTemplateTest.php | 6 +-- tests/Entity/PageTest.php | 15 +++--- tests/Entity/SortTest.php | 52 +++++++----------- tests/Entity/TagTest.php | 1 - tests/ErrorTest.php | 1 - tests/FavouriteTest.php | 13 +---- tests/Helpers/EntityProvider.php | 53 ++++++++++++++++--- tests/{ => Helpers}/TestServiceProvider.php | 2 +- tests/HomepageTest.php | 7 +-- tests/OpenGraphTest.php | 4 -- tests/Permissions/EntityOwnerChangeTest.php | 4 -- tests/Permissions/EntityPermissionsTest.php | 11 +--- tests/Permissions/ExportPermissionsTest.php | 2 - tests/Permissions/RolesTest.php | 2 +- tests/PublicActionTest.php | 1 - tests/References/CrossLinkParserTest.php | 3 +- tests/References/ReferencesTest.php | 45 +++++----------- tests/Settings/RecycleBinTest.php | 26 ++++----- tests/TestCase.php | 1 + tests/UrlTest.php | 3 -- tests/User/UserManagementTest.php | 1 - tests/User/UserPreferencesTest.php | 2 - 47 files changed, 198 insertions(+), 286 deletions(-) rename tests/{ => Helpers}/TestServiceProvider.php (96%) diff --git a/app/Api/ListingResponseBuilder.php b/app/Api/ListingResponseBuilder.php index 7de5ddf07..39752e6d4 100644 --- a/app/Api/ListingResponseBuilder.php +++ b/app/Api/ListingResponseBuilder.php @@ -2,7 +2,6 @@ namespace BookStack\Api; -use BookStack\Model; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; use Illuminate\Http\JsonResponse; diff --git a/tests/Actions/AuditLogTest.php b/tests/Actions/AuditLogTest.php index f4eebb364..987e23a45 100644 --- a/tests/Actions/AuditLogTest.php +++ b/tests/Actions/AuditLogTest.php @@ -6,8 +6,6 @@ use BookStack\Actions\Activity; use BookStack\Actions\ActivityLogger; use BookStack\Actions\ActivityType; use BookStack\Auth\UserRepo; -use BookStack\Entities\Models\Chapter; -use BookStack\Entities\Models\Page; use BookStack\Entities\Repos\PageRepo; use BookStack\Entities\Tools\TrashCan; use Carbon\Carbon; @@ -15,8 +13,7 @@ use Tests\TestCase; class AuditLogTest extends TestCase { - /** @var ActivityLogger */ - protected $activityService; + protected ActivityLogger $activityService; protected function setUp(): void { diff --git a/tests/Actions/WebhookCallTest.php b/tests/Actions/WebhookCallTest.php index 7964fd8af..7ca190200 100644 --- a/tests/Actions/WebhookCallTest.php +++ b/tests/Actions/WebhookCallTest.php @@ -7,7 +7,6 @@ use BookStack\Actions\ActivityType; use BookStack\Actions\DispatchWebhookJob; use BookStack\Actions\Webhook; use BookStack\Auth\User; -use BookStack\Entities\Models\Page; use Illuminate\Http\Client\Request; use Illuminate\Support\Facades\Bus; use Illuminate\Support\Facades\Http; diff --git a/tests/Actions/WebhookFormatTesting.php b/tests/Actions/WebhookFormatTesting.php index 35467a76a..07341c75b 100644 --- a/tests/Actions/WebhookFormatTesting.php +++ b/tests/Actions/WebhookFormatTesting.php @@ -5,9 +5,6 @@ namespace Tests\Actions; use BookStack\Actions\ActivityType; use BookStack\Actions\Webhook; use BookStack\Actions\WebhookFormatter; -use BookStack\Entities\Models\Book; -use BookStack\Entities\Models\Chapter; -use BookStack\Entities\Models\Page; use Illuminate\Support\Arr; use Tests\TestCase; @@ -16,9 +13,9 @@ class WebhookFormatTesting extends TestCase public function test_entity_events_show_related_user_info() { $events = [ - ActivityType::BOOK_UPDATE => Book::query()->first(), - ActivityType::CHAPTER_CREATE => Chapter::query()->first(), - ActivityType::PAGE_MOVE => Page::query()->first(), + ActivityType::BOOK_UPDATE => $this->entities->book(), + ActivityType::CHAPTER_CREATE => $this->entities->chapter(), + ActivityType::PAGE_MOVE => $this->entities->page(), ]; foreach ($events as $event => $entity) { diff --git a/tests/Api/BooksApiTest.php b/tests/Api/BooksApiTest.php index 017322193..614185c93 100644 --- a/tests/Api/BooksApiTest.php +++ b/tests/Api/BooksApiTest.php @@ -68,7 +68,7 @@ class BooksApiTest extends TestCase public function test_read_endpoint() { $this->actingAsApiEditor(); - $book = Book::visible()->first(); + $book = $this->entities->book(); $resp = $this->getJson($this->baseEndpoint . "/{$book->id}"); @@ -91,8 +91,7 @@ class BooksApiTest extends TestCase public function test_read_endpoint_includes_chapter_and_page_contents() { $this->actingAsApiEditor(); - /** @var Book $book */ - $book = Book::visible()->has('chapters')->has('pages')->first(); + $book = $this->entities->bookHasChaptersAndPages(); $chapter = $book->chapters()->first(); $chapterPage = $chapter->pages()->first(); @@ -123,7 +122,7 @@ class BooksApiTest extends TestCase public function test_update_endpoint() { $this->actingAsApiEditor(); - $book = Book::visible()->first(); + $book = $this->entities->book(); $details = [ 'name' => 'My updated API book', 'description' => 'A book created via the API', @@ -140,7 +139,7 @@ class BooksApiTest extends TestCase public function test_update_increments_updated_date_if_only_tags_are_sent() { $this->actingAsApiEditor(); - $book = Book::visible()->first(); + $book = $this->entities->book(); DB::table('books')->where('id', '=', $book->id)->update(['updated_at' => Carbon::now()->subWeek()]); $details = [ @@ -156,7 +155,7 @@ class BooksApiTest extends TestCase { $this->actingAsApiEditor(); /** @var Book $book */ - $book = Book::visible()->first(); + $book = $this->entities->book(); $this->assertNull($book->cover); $file = $this->getTestImage('image.png'); @@ -191,7 +190,7 @@ class BooksApiTest extends TestCase public function test_delete_endpoint() { $this->actingAsApiEditor(); - $book = Book::visible()->first(); + $book = $this->entities->book(); $resp = $this->deleteJson($this->baseEndpoint . "/{$book->id}"); $resp->assertStatus(204); @@ -201,7 +200,7 @@ class BooksApiTest extends TestCase public function test_export_html_endpoint() { $this->actingAsApiEditor(); - $book = Book::visible()->first(); + $book = $this->entities->book(); $resp = $this->get($this->baseEndpoint . "/{$book->id}/export/html"); $resp->assertStatus(200); @@ -212,7 +211,7 @@ class BooksApiTest extends TestCase public function test_export_plain_text_endpoint() { $this->actingAsApiEditor(); - $book = Book::visible()->first(); + $book = $this->entities->book(); $resp = $this->get($this->baseEndpoint . "/{$book->id}/export/plaintext"); $resp->assertStatus(200); @@ -223,7 +222,7 @@ class BooksApiTest extends TestCase public function test_export_pdf_endpoint() { $this->actingAsApiEditor(); - $book = Book::visible()->first(); + $book = $this->entities->book(); $resp = $this->get($this->baseEndpoint . "/{$book->id}/export/pdf"); $resp->assertStatus(200); @@ -249,7 +248,7 @@ class BooksApiTest extends TestCase $this->actingAsApiEditor(); $this->removePermissionFromUser($this->getEditor(), 'content-export'); - $book = Book::visible()->first(); + $book = $this->entities->book(); foreach ($types as $type) { $resp = $this->get($this->baseEndpoint . "/{$book->id}/export/{$type}"); $this->assertPermissionError($resp); diff --git a/tests/Api/ChaptersApiTest.php b/tests/Api/ChaptersApiTest.php index 22be2482c..d2db0313f 100644 --- a/tests/Api/ChaptersApiTest.php +++ b/tests/Api/ChaptersApiTest.php @@ -2,7 +2,6 @@ namespace Tests\Api; -use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Chapter; use Carbon\Carbon; use Illuminate\Support\Facades\DB; @@ -95,7 +94,7 @@ class ChaptersApiTest extends TestCase public function test_read_endpoint() { $this->actingAsApiEditor(); - $chapter = Chapter::visible()->first(); + $chapter = $this->entities->chapter(); $page = $chapter->pages()->first(); $resp = $this->getJson($this->baseEndpoint . "/{$chapter->id}"); @@ -127,7 +126,7 @@ class ChaptersApiTest extends TestCase public function test_update_endpoint() { $this->actingAsApiEditor(); - $chapter = Chapter::visible()->first(); + $chapter = $this->entities->chapter(); $details = [ 'name' => 'My updated API chapter', 'description' => 'A chapter created via the API', @@ -152,7 +151,7 @@ class ChaptersApiTest extends TestCase public function test_update_increments_updated_date_if_only_tags_are_sent() { $this->actingAsApiEditor(); - $chapter = Chapter::visible()->first(); + $chapter = $this->entities->chapter(); DB::table('chapters')->where('id', '=', $chapter->id)->update(['updated_at' => Carbon::now()->subWeek()]); $details = [ @@ -167,7 +166,7 @@ class ChaptersApiTest extends TestCase public function test_delete_endpoint() { $this->actingAsApiEditor(); - $chapter = Chapter::visible()->first(); + $chapter = $this->entities->chapter(); $resp = $this->deleteJson($this->baseEndpoint . "/{$chapter->id}"); $resp->assertStatus(204); @@ -177,7 +176,7 @@ class ChaptersApiTest extends TestCase public function test_export_html_endpoint() { $this->actingAsApiEditor(); - $chapter = Chapter::visible()->first(); + $chapter = $this->entities->chapter(); $resp = $this->get($this->baseEndpoint . "/{$chapter->id}/export/html"); $resp->assertStatus(200); @@ -188,7 +187,7 @@ class ChaptersApiTest extends TestCase public function test_export_plain_text_endpoint() { $this->actingAsApiEditor(); - $chapter = Chapter::visible()->first(); + $chapter = $this->entities->chapter(); $resp = $this->get($this->baseEndpoint . "/{$chapter->id}/export/plaintext"); $resp->assertStatus(200); @@ -199,7 +198,7 @@ class ChaptersApiTest extends TestCase public function test_export_pdf_endpoint() { $this->actingAsApiEditor(); - $chapter = Chapter::visible()->first(); + $chapter = $this->entities->chapter(); $resp = $this->get($this->baseEndpoint . "/{$chapter->id}/export/pdf"); $resp->assertStatus(200); diff --git a/tests/Api/PagesApiTest.php b/tests/Api/PagesApiTest.php index fe1fc8d36..8c533680f 100644 --- a/tests/Api/PagesApiTest.php +++ b/tests/Api/PagesApiTest.php @@ -2,7 +2,6 @@ namespace Tests\Api; -use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Page; use Carbon\Carbon; @@ -95,11 +94,11 @@ class PagesApiTest extends TestCase 'chapter_id' => ['The chapter id field is required when book id is not present.'], ])); - $chapter = Chapter::visible()->first(); + $chapter = $this->entities->chapter(); $resp = $this->postJson($this->baseEndpoint, array_merge($details, ['chapter_id' => $chapter->id])); $resp->assertStatus(200); - $book = Book::visible()->first(); + $book = $this->entities->book(); $resp = $this->postJson($this->baseEndpoint, array_merge($details, ['book_id' => $book->id])); $resp->assertStatus(200); } @@ -107,7 +106,7 @@ class PagesApiTest extends TestCase public function test_markdown_can_be_provided_for_create() { $this->actingAsApiEditor(); - $book = Book::visible()->first(); + $book = $this->entities->book(); $details = [ 'book_id' => $book->id, 'name' => 'My api page', @@ -126,7 +125,7 @@ class PagesApiTest extends TestCase public function test_read_endpoint() { $this->actingAsApiEditor(); - $page = Page::visible()->first(); + $page = $this->entities->page(); $resp = $this->getJson($this->baseEndpoint . "/{$page->id}"); $resp->assertStatus(200); @@ -149,7 +148,7 @@ class PagesApiTest extends TestCase public function test_read_endpoint_provides_rendered_html() { $this->actingAsApiEditor(); - $page = Page::visible()->first(); + $page = $this->entities->page(); $page->html = "

testing

Hello

"; $page->save(); @@ -163,7 +162,7 @@ class PagesApiTest extends TestCase public function test_update_endpoint() { $this->actingAsApiEditor(); - $page = Page::visible()->first(); + $page = $this->entities->page(); $details = [ 'name' => 'My updated API page', 'html' => '

A page created via the API

', @@ -189,7 +188,7 @@ class PagesApiTest extends TestCase public function test_providing_new_chapter_id_on_update_will_move_page() { $this->actingAsApiEditor(); - $page = Page::visible()->first(); + $page = $this->entities->page(); $chapter = Chapter::visible()->where('book_id', '!=', $page->book_id)->first(); $details = [ 'name' => 'My updated API page', @@ -208,7 +207,7 @@ class PagesApiTest extends TestCase public function test_providing_move_via_update_requires_page_create_permission_on_new_parent() { $this->actingAsApiEditor(); - $page = Page::visible()->first(); + $page = $this->entities->page(); $chapter = Chapter::visible()->where('book_id', '!=', $page->book_id)->first(); $this->entities->setPermissions($chapter, ['view'], [$this->getEditor()->roles()->first()]); $details = [ @@ -224,7 +223,7 @@ class PagesApiTest extends TestCase public function test_update_endpoint_does_not_wipe_content_if_no_html_or_md_provided() { $this->actingAsApiEditor(); - $page = Page::visible()->first(); + $page = $this->entities->page(); $originalContent = $page->html; $details = [ 'name' => 'My updated API page', @@ -245,7 +244,7 @@ class PagesApiTest extends TestCase public function test_update_increments_updated_date_if_only_tags_are_sent() { $this->actingAsApiEditor(); - $page = Page::visible()->first(); + $page = $this->entities->page(); DB::table('pages')->where('id', '=', $page->id)->update(['updated_at' => Carbon::now()->subWeek()]); $details = [ @@ -262,7 +261,7 @@ class PagesApiTest extends TestCase public function test_delete_endpoint() { $this->actingAsApiEditor(); - $page = Page::visible()->first(); + $page = $this->entities->page(); $resp = $this->deleteJson($this->baseEndpoint . "/{$page->id}"); $resp->assertStatus(204); @@ -272,7 +271,7 @@ class PagesApiTest extends TestCase public function test_export_html_endpoint() { $this->actingAsApiEditor(); - $page = Page::visible()->first(); + $page = $this->entities->page(); $resp = $this->get($this->baseEndpoint . "/{$page->id}/export/html"); $resp->assertStatus(200); @@ -283,7 +282,7 @@ class PagesApiTest extends TestCase public function test_export_plain_text_endpoint() { $this->actingAsApiEditor(); - $page = Page::visible()->first(); + $page = $this->entities->page(); $resp = $this->get($this->baseEndpoint . "/{$page->id}/export/plaintext"); $resp->assertStatus(200); @@ -294,7 +293,7 @@ class PagesApiTest extends TestCase public function test_export_pdf_endpoint() { $this->actingAsApiEditor(); - $page = Page::visible()->first(); + $page = $this->entities->page(); $resp = $this->get($this->baseEndpoint . "/{$page->id}/export/pdf"); $resp->assertStatus(200); @@ -304,7 +303,7 @@ class PagesApiTest extends TestCase public function test_export_markdown_endpoint() { $this->actingAsApiEditor(); - $page = Page::visible()->first(); + $page = $this->entities->page(); $resp = $this->get($this->baseEndpoint . "/{$page->id}/export/markdown"); $resp->assertStatus(200); @@ -318,7 +317,7 @@ class PagesApiTest extends TestCase $this->actingAsApiEditor(); $this->removePermissionFromUser($this->getEditor(), 'content-export'); - $page = Page::visible()->first(); + $page = $this->entities->page(); foreach ($types as $type) { $resp = $this->get($this->baseEndpoint . "/{$page->id}/export/{$type}"); $this->assertPermissionError($resp); diff --git a/tests/Api/RecycleBinApiTest.php b/tests/Api/RecycleBinApiTest.php index cdb51f85a..bc7249987 100644 --- a/tests/Api/RecycleBinApiTest.php +++ b/tests/Api/RecycleBinApiTest.php @@ -4,7 +4,6 @@ namespace Tests\Api; use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Deletion; -use BookStack\Entities\Models\Page; use Illuminate\Support\Collection; use Tests\TestCase; @@ -111,7 +110,7 @@ class RecycleBinApiTest extends TestCase public function test_index_endpoint_returns_parent() { $admin = $this->getAdmin(); - $page = Page::query()->whereHas('chapter')->with('chapter')->first(); + $page = $this->entities->pageWithinChapter(); $this->actingAs($admin)->delete($page->getUrl()); $deletion = Deletion::query()->orderBy('id')->first(); diff --git a/tests/Auth/AuthTest.php b/tests/Auth/AuthTest.php index 4456ed459..3220b2aac 100644 --- a/tests/Auth/AuthTest.php +++ b/tests/Auth/AuthTest.php @@ -3,7 +3,6 @@ namespace Tests\Auth; use BookStack\Auth\Access\Mfa\MfaSession; -use BookStack\Entities\Models\Page; use Illuminate\Testing\TestResponse; use Tests\TestCase; diff --git a/tests/Commands/ClearActivityCommandTest.php b/tests/Commands/ClearActivityCommandTest.php index abc8bc7f4..cf2fba0d6 100644 --- a/tests/Commands/ClearActivityCommandTest.php +++ b/tests/Commands/ClearActivityCommandTest.php @@ -3,7 +3,6 @@ namespace Tests\Commands; use BookStack\Actions\ActivityType; -use BookStack\Entities\Models\Page; use BookStack\Facades\Activity; use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\DB; diff --git a/tests/Commands/CopyShelfPermissionsCommandTest.php b/tests/Commands/CopyShelfPermissionsCommandTest.php index bd96f2cc5..55b710ba9 100644 --- a/tests/Commands/CopyShelfPermissionsCommandTest.php +++ b/tests/Commands/CopyShelfPermissionsCommandTest.php @@ -16,7 +16,7 @@ class CopyShelfPermissionsCommandTest extends TestCase public function test_copy_shelf_permissions_command_using_slug() { - $shelf = Bookshelf::first(); + $shelf = $this->entities->shelf(); $child = $shelf->books()->first(); $editorRole = $this->getEditor()->roles()->first(); $this->assertFalse(boolval($child->restricted), 'Child book should not be restricted by default'); diff --git a/tests/Commands/RegenerateReferencesCommandTest.php b/tests/Commands/RegenerateReferencesCommandTest.php index 2c737712a..36af0d7cc 100644 --- a/tests/Commands/RegenerateReferencesCommandTest.php +++ b/tests/Commands/RegenerateReferencesCommandTest.php @@ -2,7 +2,6 @@ namespace Tests\Commands; -use BookStack\Entities\Models\Page; use Illuminate\Support\Facades\DB; use Tests\TestCase; diff --git a/tests/Commands/UpdateUrlCommandTest.php b/tests/Commands/UpdateUrlCommandTest.php index c4b09162e..c07a80312 100644 --- a/tests/Commands/UpdateUrlCommandTest.php +++ b/tests/Commands/UpdateUrlCommandTest.php @@ -2,7 +2,6 @@ namespace Tests\Commands; -use BookStack\Entities\Models\Page; use Symfony\Component\Console\Exception\RuntimeException; use Tests\TestCase; diff --git a/tests/Entity/BookShelfTest.php b/tests/Entity/BookShelfTest.php index 798edeadf..1e740b94e 100644 --- a/tests/Entity/BookShelfTest.php +++ b/tests/Entity/BookShelfTest.php @@ -39,7 +39,7 @@ class BookShelfTest extends TestCase { $user = User::factory()->create(); $this->giveUserPermissions($user, ['image-create-all']); - $shelf = Bookshelf::first(); + $shelf = $this->entities->shelf(); $userRole = $user->roles()->first(); $resp = $this->actingAs($user)->get('/'); @@ -130,7 +130,7 @@ class BookShelfTest extends TestCase public function test_shelf_view() { - $shelf = Bookshelf::first(); + $shelf = $this->entities->shelf(); $resp = $this->asEditor()->get($shelf->getUrl()); $resp->assertStatus(200); $resp->assertSeeText($shelf->name); @@ -143,7 +143,7 @@ class BookShelfTest extends TestCase public function test_shelf_view_shows_action_buttons() { - $shelf = Bookshelf::first(); + $shelf = $this->entities->shelf(); $resp = $this->asAdmin()->get($shelf->getUrl()); $resp->assertSee($shelf->getUrl('/create-book')); $resp->assertSee($shelf->getUrl('/edit')); @@ -201,7 +201,7 @@ class BookShelfTest extends TestCase public function test_shelf_edit() { - $shelf = Bookshelf::first(); + $shelf = $this->entities->shelf(); $resp = $this->asEditor()->get($shelf->getUrl('/edit')); $resp->assertSeeText('Edit Shelf'); @@ -239,7 +239,7 @@ class BookShelfTest extends TestCase public function test_shelf_create_new_book() { - $shelf = Bookshelf::first(); + $shelf = $this->entities->shelf(); $resp = $this->asEditor()->get($shelf->getUrl('/create-book')); $resp->assertSee('Create New Book'); @@ -288,7 +288,7 @@ class BookShelfTest extends TestCase public function test_shelf_copy_permissions() { - $shelf = Bookshelf::first(); + $shelf = $this->entities->shelf(); $resp = $this->asAdmin()->get($shelf->getUrl('/permissions')); $resp->assertSeeText('Copy Permissions'); $resp->assertSee("action=\"{$shelf->getUrl('/copy-permissions')}\"", false); @@ -311,14 +311,14 @@ class BookShelfTest extends TestCase public function test_permission_page_has_a_warning_about_no_cascading() { - $shelf = Bookshelf::first(); + $shelf = $this->entities->shelf(); $resp = $this->asAdmin()->get($shelf->getUrl('/permissions')); $resp->assertSeeText('Permissions on shelves do not automatically cascade to contained books.'); } public function test_bookshelves_show_in_breadcrumbs_if_in_context() { - $shelf = Bookshelf::first(); + $shelf = $this->entities->shelf(); $shelfBook = $shelf->books()->first(); $shelfPage = $shelfBook->pages()->first(); $this->asAdmin(); diff --git a/tests/Entity/ChapterTest.php b/tests/Entity/ChapterTest.php index fc8adb01d..afc60c20e 100644 --- a/tests/Entity/ChapterTest.php +++ b/tests/Entity/ChapterTest.php @@ -96,8 +96,7 @@ class ChapterTest extends TestCase public function test_copy_does_not_copy_non_visible_pages() { - /** @var Chapter $chapter */ - $chapter = Chapter::query()->whereHas('pages')->first(); + $chapter = $this->entities->chapterHasPages(); // Hide pages to all non-admin roles /** @var Page $page */ @@ -118,8 +117,7 @@ class ChapterTest extends TestCase public function test_copy_does_not_copy_pages_if_user_cant_page_create() { - /** @var Chapter $chapter */ - $chapter = Chapter::query()->whereHas('pages')->first(); + $chapter = $this->entities->chapterHasPages(); $viewer = $this->getViewer(); $this->giveUserPermissions($viewer, ['chapter-create-all']); diff --git a/tests/Entity/CommentSettingTest.php b/tests/Entity/CommentSettingTest.php index 0e3199979..7de457441 100644 --- a/tests/Entity/CommentSettingTest.php +++ b/tests/Entity/CommentSettingTest.php @@ -2,34 +2,27 @@ namespace Tests\Entity; -use BookStack\Entities\Models\Page; use Tests\TestCase; class CommentSettingTest extends TestCase { - protected $page; - - protected function setUp(): void - { - parent::setUp(); - $this->page = Page::query()->first(); - } - public function test_comment_disable() { + $page = $this->entities->page(); $this->setSettings(['app-disable-comments' => 'true']); $this->asAdmin(); - $resp = $this->asAdmin()->get($this->page->getUrl()); + $resp = $this->asAdmin()->get($page->getUrl()); $this->withHtml($resp)->assertElementNotExists('.comments-list'); } public function test_comment_enable() { + $page = $this->entities->page(); $this->setSettings(['app-disable-comments' => 'false']); $this->asAdmin(); - $resp = $this->asAdmin()->get($this->page->getUrl()); + $resp = $this->asAdmin()->get($page->getUrl()); $this->withHtml($resp)->assertElementExists('.comments-list'); } } diff --git a/tests/Entity/CommentTest.php b/tests/Entity/CommentTest.php index 1e8ecbcac..99e3525a0 100644 --- a/tests/Entity/CommentTest.php +++ b/tests/Entity/CommentTest.php @@ -11,7 +11,7 @@ class CommentTest extends TestCase public function test_add_comment() { $this->asAdmin(); - $page = Page::first(); + $page = $this->entities->page(); $comment = Comment::factory()->make(['parent_id' => 2]); $resp = $this->postJson("/comment/$page->id", $comment->getAttributes()); @@ -34,7 +34,7 @@ class CommentTest extends TestCase public function test_comment_edit() { $this->asAdmin(); - $page = Page::first(); + $page = $this->entities->page(); $comment = Comment::factory()->make(); $this->postJson("/comment/$page->id", $comment->getAttributes()); @@ -58,7 +58,7 @@ class CommentTest extends TestCase public function test_comment_delete() { $this->asAdmin(); - $page = Page::first(); + $page = $this->entities->page(); $comment = Comment::factory()->make(); $this->postJson("/comment/$page->id", $comment->getAttributes()); @@ -75,7 +75,7 @@ class CommentTest extends TestCase public function test_comments_converts_markdown_input_to_html() { - $page = Page::first(); + $page = $this->entities->page(); $this->asAdmin()->postJson("/comment/$page->id", [ 'text' => '# My Title', ]); @@ -94,7 +94,7 @@ class CommentTest extends TestCase public function test_html_cannot_be_injected_via_comment_content() { $this->asAdmin(); - $page = Page::first(); + $page = $this->entities->page(); $script = '\n\n# sometextinthecomment'; $this->postJson("/comment/$page->id", [ diff --git a/tests/Entity/ConvertTest.php b/tests/Entity/ConvertTest.php index 15205c9ad..16dd89068 100644 --- a/tests/Entity/ConvertTest.php +++ b/tests/Entity/ConvertTest.php @@ -24,8 +24,7 @@ class ConvertTest extends TestCase public function test_convert_chapter_to_book() { - /** @var Chapter $chapter */ - $chapter = Chapter::query()->whereHas('pages')->first(); + $chapter = $this->entities->chapterHasPages(); $chapter->tags()->save(new Tag(['name' => 'Category', 'value' => 'Penguins'])); /** @var Page $childPage */ $childPage = $chapter->pages()->first(); diff --git a/tests/Entity/EntityAccessTest.php b/tests/Entity/EntityAccessTest.php index e3d129d5e..2bb32fde8 100644 --- a/tests/Entity/EntityAccessTest.php +++ b/tests/Entity/EntityAccessTest.php @@ -4,7 +4,6 @@ namespace Tests\Entity; use BookStack\Auth\UserRepo; use BookStack\Entities\Models\Entity; -use BookStack\Entities\Repos\PageRepo; use Tests\TestCase; class EntityAccessTest extends TestCase @@ -16,7 +15,7 @@ class EntityAccessTest extends TestCase $updater = $this->getViewer(); $entities = $this->entities->createChainBelongingToUser($creator, $updater); app()->make(UserRepo::class)->destroy($creator); - app()->make(PageRepo::class)->update($entities['page'], ['html' => '

hello!

>']); + $this->entities->updatePage($entities['page'], ['html' => '

hello!

>']); $this->checkEntitiesViewable($entities); } @@ -28,7 +27,7 @@ class EntityAccessTest extends TestCase $updater = $this->getEditor(); $entities = $this->entities->createChainBelongingToUser($creator, $updater); app()->make(UserRepo::class)->destroy($updater); - app()->make(PageRepo::class)->update($entities['page'], ['html' => '

Hello there!

']); + $this->entities->updatePage($entities['page'], ['html' => '

Hello there!

']); $this->checkEntitiesViewable($entities); } diff --git a/tests/Entity/EntitySearchTest.php b/tests/Entity/EntitySearchTest.php index 82b97e6f3..cdb500a45 100644 --- a/tests/Entity/EntitySearchTest.php +++ b/tests/Entity/EntitySearchTest.php @@ -5,15 +5,13 @@ namespace Tests\Entity; use BookStack\Actions\Tag; use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Bookshelf; -use BookStack\Entities\Models\Chapter; -use BookStack\Entities\Models\Page; use Tests\TestCase; class EntitySearchTest extends TestCase { public function test_page_search() { - $book = Book::all()->first(); + $book = $this->entities->book(); $page = $book->pages->first(); $search = $this->asEditor()->get('/search?term=' . urlencode($page->name)); @@ -71,7 +69,7 @@ class EntitySearchTest extends TestCase public function test_chapter_search() { - $chapter = Chapter::has('pages')->first(); + $chapter = $this->entities->chapterHasPages(); $page = $chapter->pages[0]; $pageTestResp = $this->asEditor()->get('/search/chapter/' . $chapter->id . '?term=' . urlencode($page->name)); @@ -91,10 +89,10 @@ class EntitySearchTest extends TestCase ]), ]; - $pageA = Page::first(); + $pageA = $this->entities->page(); $pageA->tags()->saveMany($newTags); - $pageB = Page::all()->last(); + $pageB = $this->entities->page(); $pageB->tags()->create(['name' => 'animal', 'value' => 'dog']); $this->asEditor(); @@ -197,7 +195,7 @@ class EntitySearchTest extends TestCase public function test_ajax_entity_search() { $page = $this->entities->newPage(['name' => 'my ajax search test', 'html' => 'ajax test']); - $notVisitedPage = Page::first(); + $notVisitedPage = $this->entities->page(); // Visit the page to make popular $this->asEditor()->get($page->getUrl()); @@ -215,7 +213,7 @@ class EntitySearchTest extends TestCase public function test_ajax_entity_search_shows_breadcrumbs() { - $chapter = Chapter::first(); + $chapter = $this->entities->chapter(); $page = $chapter->pages->first(); $this->asEditor(); @@ -246,7 +244,7 @@ class EntitySearchTest extends TestCase public function test_sibling_search_for_pages() { - $chapter = Chapter::query()->with('pages')->first(); + $chapter = $this->entities->chapterHasPages(); $this->assertGreaterThan(2, count($chapter->pages), 'Ensure we\'re testing with at least 1 sibling'); $page = $chapter->pages->first(); @@ -261,7 +259,7 @@ class EntitySearchTest extends TestCase public function test_sibling_search_for_pages_without_chapter() { - $page = Page::query()->where('chapter_id', '=', 0)->firstOrFail(); + $page = $this->entities->pageNotWithinChapter(); $bookChildren = $page->book->getDirectChildren(); $this->assertGreaterThan(2, count($bookChildren), 'Ensure we\'re testing with at least 1 sibling'); @@ -276,7 +274,7 @@ class EntitySearchTest extends TestCase public function test_sibling_search_for_chapters() { - $chapter = Chapter::query()->firstOrFail(); + $chapter = $this->entities->chapter(); $bookChildren = $chapter->book->getDirectChildren(); $this->assertGreaterThan(2, count($bookChildren), 'Ensure we\'re testing with at least 1 sibling'); diff --git a/tests/Entity/ExportTest.php b/tests/Entity/ExportTest.php index 1d4a23560..0f80bdd49 100644 --- a/tests/Entity/ExportTest.php +++ b/tests/Entity/ExportTest.php @@ -311,7 +311,7 @@ class ExportTest extends TestCase public function test_page_pdf_export_opens_details_blocks() { - $page = Page::query()->first()->forceFill([ + $page = $this->entities->page()->forceFill([ 'html' => '
Hello

Content!

', ]); $page->save(); @@ -339,7 +339,7 @@ class ExportTest extends TestCase public function test_page_markdown_export_uses_existing_markdown_if_apparent() { - $page = Page::query()->first()->forceFill([ + $page = $this->entities->page()->forceFill([ 'markdown' => '# A header', 'html' => '

Dogcat

', ]); @@ -352,7 +352,7 @@ class ExportTest extends TestCase public function test_page_markdown_export_converts_html_where_no_markdown() { - $page = Page::query()->first()->forceFill([ + $page = $this->entities->page()->forceFill([ 'markdown' => '', 'html' => '

Dogcat

Some bold text

', ]); @@ -446,9 +446,9 @@ class ExportTest extends TestCase public function test_html_exports_contain_csp_meta_tag() { $entities = [ - Page::query()->first(), - Book::query()->first(), - Chapter::query()->first(), + $this->entities->page(), + $this->entities->book(), + $this->entities->chapter(), ]; foreach ($entities as $entity) { diff --git a/tests/Entity/PageContentTest.php b/tests/Entity/PageContentTest.php index c6de4dc51..0c9854206 100644 --- a/tests/Entity/PageContentTest.php +++ b/tests/Entity/PageContentTest.php @@ -16,7 +16,7 @@ class PageContentTest extends TestCase public function test_page_includes() { $page = $this->entities->page(); - $secondPage = Page::query()->where('id', '!=', $page->id)->first(); + $secondPage = $this->entities->page(); $secondPage->html = "

Hello, This is a test

This is a second block of content

"; $secondPage->save(); @@ -45,7 +45,7 @@ class PageContentTest extends TestCase public function test_saving_page_with_includes() { $page = $this->entities->page(); - $secondPage = Page::query()->where('id', '!=', $page->id)->first(); + $secondPage = $this->entities->page(); $this->asEditor(); $includeTag = '{{@' . $secondPage->id . '}}'; diff --git a/tests/Entity/PageDraftTest.php b/tests/Entity/PageDraftTest.php index acf6b01e8..010173852 100644 --- a/tests/Entity/PageDraftTest.php +++ b/tests/Entity/PageDraftTest.php @@ -2,7 +2,6 @@ namespace Tests\Entity; -use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Page; use BookStack\Entities\Models\PageRevision; use BookStack\Entities\Repos\PageRepo; @@ -10,20 +9,13 @@ use Tests\TestCase; class PageDraftTest extends TestCase { - /** - * @var Page - */ - protected $page; - - /** - * @var PageRepo - */ - protected $pageRepo; + protected Page $page; + protected PageRepo $pageRepo; protected function setUp(): void { parent::setUp(); - $this->page = Page::query()->first(); + $this->page = $this->entities->page(); $this->pageRepo = app()->make(PageRepo::class); } diff --git a/tests/Entity/PageEditorTest.php b/tests/Entity/PageEditorTest.php index 6ce649a54..b2fb85955 100644 --- a/tests/Entity/PageEditorTest.php +++ b/tests/Entity/PageEditorTest.php @@ -2,20 +2,18 @@ namespace Tests\Entity; -use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Page; use Tests\TestCase; class PageEditorTest extends TestCase { - /** @var Page */ - protected $page; + protected Page $page; protected function setUp(): void { parent::setUp(); - $this->page = Page::query()->first(); + $this->page = $this->entities->page(); } public function test_default_editor_is_wysiwyg_for_new_pages() @@ -80,8 +78,7 @@ class PageEditorTest extends TestCase public function test_back_link_in_editor_has_correct_url() { - /** @var Book $book */ - $book = Book::query()->whereHas('pages')->whereHas('chapters')->firstOrFail(); + $book = $this->entities->bookHasChaptersAndPages(); $this->asEditor()->get($book->getUrl('/create-page')); /** @var Chapter $chapter */ $chapter = $book->chapters()->firstOrFail(); diff --git a/tests/Entity/PageRevisionTest.php b/tests/Entity/PageRevisionTest.php index eabece4c6..d00ec5ce5 100644 --- a/tests/Entity/PageRevisionTest.php +++ b/tests/Entity/PageRevisionTest.php @@ -21,7 +21,7 @@ class PageRevisionTest extends TestCase public function test_page_revision_views_viewable() { $this->asEditor(); - $page = Page::first(); + $page = $this->entities->page(); $this->createRevisions($page, 1, ['name' => 'updated page', 'html' => '

new content

']); $pageRevision = $page->revisions->last(); @@ -37,7 +37,7 @@ class PageRevisionTest extends TestCase public function test_page_revision_preview_shows_content_of_revision() { $this->asEditor(); - $page = Page::first(); + $page = $this->entities->page(); $this->createRevisions($page, 1, ['name' => 'updated page', 'html' => '

new revision content

']); $pageRevision = $page->revisions->last(); $this->createRevisions($page, 1, ['name' => 'updated page', 'html' => '

Updated content

']); @@ -50,7 +50,7 @@ class PageRevisionTest extends TestCase public function test_page_revision_restore_updates_content() { $this->asEditor(); - $page = Page::first(); + $page = $this->entities->page(); $this->createRevisions($page, 1, ['name' => 'updated page abc123', 'html' => '

new contente def456

']); $this->createRevisions($page, 1, ['name' => 'updated page again', 'html' => '

new content

']); $page = Page::find($page->id); @@ -74,7 +74,7 @@ class PageRevisionTest extends TestCase public function test_page_revision_restore_with_markdown_retains_markdown_content() { $this->asEditor(); - $page = Page::first(); + $page = $this->entities->page(); $this->createRevisions($page, 1, ['name' => 'updated page abc123', 'markdown' => '## New Content def456']); $this->createRevisions($page, 1, ['name' => 'updated page again', 'markdown' => '## New Content Updated']); $page = Page::find($page->id); @@ -102,7 +102,7 @@ class PageRevisionTest extends TestCase public function test_page_revision_restore_sets_new_revision_with_summary() { $this->asEditor(); - $page = Page::first(); + $page = $this->entities->page(); $this->createRevisions($page, 1, ['name' => 'updated page abc123', 'html' => '

new contente def456

', 'summary' => 'My first update']); $this->createRevisions($page, 1, ['html' => '

new content

']); $page->refresh(); @@ -124,7 +124,7 @@ class PageRevisionTest extends TestCase public function test_page_revision_count_increments_on_update() { - $page = Page::first(); + $page = $this->entities->page(); $startCount = $page->revision_count; $this->createRevisions($page, 1); @@ -133,7 +133,7 @@ class PageRevisionTest extends TestCase public function test_revision_count_shown_in_page_meta() { - $page = Page::first(); + $page = $this->entities->page(); $this->createRevisions($page, 2); $pageView = $this->get($page->getUrl()); @@ -172,7 +172,7 @@ class PageRevisionTest extends TestCase public function test_revision_limit_enforced() { config()->set('app.revision_limit', 2); - $page = Page::first(); + $page = $this->entities->page(); $this->createRevisions($page, 12); $revisionCount = $page->revisions()->count(); @@ -182,7 +182,7 @@ class PageRevisionTest extends TestCase public function test_false_revision_limit_allows_many_revisions() { config()->set('app.revision_limit', false); - $page = Page::first(); + $page = $this->entities->page(); $this->createRevisions($page, 12); $revisionCount = $page->revisions()->count(); @@ -191,8 +191,7 @@ class PageRevisionTest extends TestCase public function test_revision_list_shows_editor_type() { - /** @var Page $page */ - $page = Page::first(); + $page = $this->entities->page(); $this->createRevisions($page, 1, ['html' => 'new page html']); $resp = $this->asAdmin()->get($page->refresh()->getUrl('/revisions')); diff --git a/tests/Entity/PageTemplateTest.php b/tests/Entity/PageTemplateTest.php index 3d1689510..dc45fcfb8 100644 --- a/tests/Entity/PageTemplateTest.php +++ b/tests/Entity/PageTemplateTest.php @@ -9,7 +9,7 @@ class PageTemplateTest extends TestCase { public function test_active_templates_visible_on_page_view() { - $page = Page::first(); + $page = $this->entities->page(); $this->asEditor(); $templateView = $this->get($page->getUrl()); @@ -24,7 +24,7 @@ class PageTemplateTest extends TestCase public function test_manage_templates_permission_required_to_change_page_template_status() { - $page = Page::first(); + $page = $this->entities->page(); $editor = $this->getEditor(); $this->actingAs($editor); @@ -52,7 +52,7 @@ class PageTemplateTest extends TestCase public function test_templates_content_should_be_fetchable_only_if_page_marked_as_template() { $content = '
my_custom_template_content
'; - $page = Page::first(); + $page = $this->entities->page(); $editor = $this->getEditor(); $this->actingAs($editor); diff --git a/tests/Entity/PageTest.php b/tests/Entity/PageTest.php index 067fceeb4..f481ffb61 100644 --- a/tests/Entity/PageTest.php +++ b/tests/Entity/PageTest.php @@ -3,7 +3,6 @@ namespace Tests\Entity; use BookStack\Entities\Models\Book; -use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Page; use Carbon\Carbon; use Tests\TestCase; @@ -128,7 +127,7 @@ class PageTest extends TestCase public function test_page_copy() { - $page = Page::first(); + $page = $this->entities->page(); $page->html = '

This is some test content

'; $page->save(); @@ -151,7 +150,7 @@ class PageTest extends TestCase public function test_page_copy_with_markdown_has_both_html_and_markdown() { - $page = Page::first(); + $page = $this->entities->page(); $page->html = '

This is some test content

'; $page->markdown = '# This is some test content'; $page->save(); @@ -169,7 +168,7 @@ class PageTest extends TestCase public function test_page_copy_with_no_destination() { - $page = Page::first(); + $page = $this->entities->page(); $currentBook = $page->book; $resp = $this->asEditor()->get($page->getUrl('/copy')); @@ -188,7 +187,7 @@ class PageTest extends TestCase public function test_page_can_be_copied_without_edit_permission() { - $page = Page::first(); + $page = $this->entities->page(); $currentBook = $page->book; $newBook = Book::where('id', '!=', $currentBook->id)->first(); $viewer = $this->getViewer(); @@ -274,8 +273,7 @@ class PageTest extends TestCase public function test_recently_updated_pages_view_shows_parent_chain() { $user = $this->getEditor(); - /** @var Page $page */ - $page = Page::query()->whereNotNull('chapter_id')->first(); + $page = $this->entities->pageWithinChapter(); $this->actingAs($user)->put($page->getUrl(), [ 'name' => 'Updated title', @@ -290,8 +288,7 @@ class PageTest extends TestCase public function test_recently_updated_pages_view_does_not_show_parent_if_not_visible() { $user = $this->getEditor(); - /** @var Page $page */ - $page = Page::query()->whereNotNull('chapter_id')->first(); + $page = $this->entities->pageWithinChapter(); $this->actingAs($user)->put($page->getUrl(), [ 'name' => 'Updated title', diff --git a/tests/Entity/SortTest.php b/tests/Entity/SortTest.php index 83a8f7005..f02e15d21 100644 --- a/tests/Entity/SortTest.php +++ b/tests/Entity/SortTest.php @@ -10,24 +10,17 @@ use Tests\TestCase; class SortTest extends TestCase { - protected $book; - - protected function setUp(): void - { - parent::setUp(); - $this->book = Book::first(); - } - public function test_drafts_do_not_show_up() { $this->asAdmin(); $pageRepo = app(PageRepo::class); - $draft = $pageRepo->getNewDraftPage($this->book); + $book = $this->entities->book(); + $draft = $pageRepo->getNewDraftPage($book); - $resp = $this->get($this->book->getUrl()); + $resp = $this->get($book->getUrl()); $resp->assertSee($draft->name); - $resp = $this->get($this->book->getUrl() . '/sort'); + $resp = $this->get($book->getUrl() . '/sort'); $resp->assertDontSee($draft->name); } @@ -43,7 +36,7 @@ class SortTest extends TestCase $movePageResp = $this->put($page->getUrl('/move'), [ 'entity_selection' => 'book:' . $newBook->id, ]); - $page = Page::query()->find($page->id); + $page->refresh(); $movePageResp->assertRedirect($page->getUrl()); $this->assertTrue($page->book->id == $newBook->id, 'Page book is now the new book'); @@ -63,7 +56,7 @@ class SortTest extends TestCase $movePageResp = $this->actingAs($this->getEditor())->put($page->getUrl('/move'), [ 'entity_selection' => 'chapter:' . $newChapter->id, ]); - $page = Page::query()->find($page->id); + $page->refresh(); $movePageResp->assertRedirect($page->getUrl()); $this->assertTrue($page->book->id == $newBook->id, 'Page parent is now the new chapter'); @@ -110,7 +103,7 @@ class SortTest extends TestCase 'entity_selection' => 'book:' . $newBook->id, ]); - $page = Page::query()->find($page->id); + $page->refresh(); $movePageResp->assertRedirect($page->getUrl()); $this->assertTrue($page->book->id == $newBook->id, 'Page book is now the new book'); @@ -138,7 +131,7 @@ class SortTest extends TestCase 'entity_selection' => 'book:' . $newBook->id, ]); - $page = Page::query()->find($page->id); + $page->refresh(); $movePageResp->assertRedirect($page->getUrl()); $this->assertTrue($page->book->id == $newBook->id, 'Page book is now the new book'); } @@ -243,8 +236,7 @@ class SortTest extends TestCase public function test_book_sort_page_shows() { - /** @var Book $bookToSort */ - $bookToSort = Book::query()->first(); + $bookToSort = $this->entities->book(); $resp = $this->asAdmin()->get($bookToSort->getUrl()); $this->withHtml($resp)->assertElementExists('a[href="' . $bookToSort->getUrl('/sort') . '"]'); @@ -256,7 +248,7 @@ class SortTest extends TestCase public function test_book_sort() { - $oldBook = Book::query()->first(); + $oldBook = $this->entities->book(); $chapterToMove = $this->entities->newChapter(['name' => 'chapter to move'], $oldBook); $newBook = $this->entities->newBook(['name' => 'New sort book']); $pagesToMove = Page::query()->take(5)->get(); @@ -299,8 +291,7 @@ class SortTest extends TestCase public function test_book_sort_makes_no_changes_if_new_chapter_does_not_align_with_new_book() { - /** @var Page $page */ - $page = Page::query()->where('chapter_id', '!=', 0)->first(); + $page = $this->entities->pageWithinChapter(); $otherChapter = Chapter::query()->where('book_id', '!=', $page->book_id)->first(); $sortData = [ @@ -319,8 +310,7 @@ class SortTest extends TestCase public function test_book_sort_makes_no_changes_if_no_view_permissions_on_new_chapter() { - /** @var Page $page */ - $page = Page::query()->where('chapter_id', '!=', 0)->first(); + $page = $this->entities->pageWithinChapter(); /** @var Chapter $otherChapter */ $otherChapter = Chapter::query()->where('book_id', '!=', $page->book_id)->first(); $this->entities->setPermissions($otherChapter); @@ -341,8 +331,7 @@ class SortTest extends TestCase public function test_book_sort_makes_no_changes_if_no_view_permissions_on_new_book() { - /** @var Page $page */ - $page = Page::query()->where('chapter_id', '!=', 0)->first(); + $page = $this->entities->pageWithinChapter(); /** @var Chapter $otherChapter */ $otherChapter = Chapter::query()->where('book_id', '!=', $page->book_id)->first(); $editor = $this->getEditor(); @@ -364,8 +353,7 @@ class SortTest extends TestCase public function test_book_sort_makes_no_changes_if_no_update_or_create_permissions_on_new_chapter() { - /** @var Page $page */ - $page = Page::query()->where('chapter_id', '!=', 0)->first(); + $page = $this->entities->pageWithinChapter(); /** @var Chapter $otherChapter */ $otherChapter = Chapter::query()->where('book_id', '!=', $page->book_id)->first(); $editor = $this->getEditor(); @@ -387,8 +375,7 @@ class SortTest extends TestCase public function test_book_sort_makes_no_changes_if_no_update_permissions_on_moved_item() { - /** @var Page $page */ - $page = Page::query()->where('chapter_id', '!=', 0)->first(); + $page = $this->entities->pageWithinChapter(); /** @var Chapter $otherChapter */ $otherChapter = Chapter::query()->where('book_id', '!=', $page->book_id)->first(); $editor = $this->getEditor(); @@ -410,8 +397,7 @@ class SortTest extends TestCase public function test_book_sort_makes_no_changes_if_no_delete_permissions_on_moved_item() { - /** @var Page $page */ - $page = Page::query()->where('chapter_id', '!=', 0)->first(); + $page = $this->entities->pageWithinChapter(); /** @var Chapter $otherChapter */ $otherChapter = Chapter::query()->where('book_id', '!=', $page->book_id)->first(); $editor = $this->getEditor(); @@ -433,8 +419,7 @@ class SortTest extends TestCase public function test_book_sort_item_returns_book_content() { - $books = Book::all(); - $bookToSort = $books[0]; + $bookToSort = $this->entities->book(); $firstPage = $bookToSort->pages[0]; $firstChapter = $bookToSort->chapters[0]; @@ -448,8 +433,7 @@ class SortTest extends TestCase public function test_pages_in_book_show_sorted_by_priority() { - /** @var Book $book */ - $book = Book::query()->whereHas('pages')->first(); + $book = $this->entities->bookHasChaptersAndPages(); $book->chapters()->forceDelete(); /** @var Page[] $pages */ $pages = $book->pages()->where('chapter_id', '=', 0)->take(2)->get(); diff --git a/tests/Entity/TagTest.php b/tests/Entity/TagTest.php index 18ee31826..ab0627601 100644 --- a/tests/Entity/TagTest.php +++ b/tests/Entity/TagTest.php @@ -3,7 +3,6 @@ namespace Tests\Entity; use BookStack\Actions\Tag; -use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\Page; use Tests\TestCase; diff --git a/tests/ErrorTest.php b/tests/ErrorTest.php index c46d65bde..ebd9874d3 100644 --- a/tests/ErrorTest.php +++ b/tests/ErrorTest.php @@ -2,7 +2,6 @@ namespace Tests; -use BookStack\Entities\Models\Book; use Illuminate\Support\Facades\Log; class ErrorTest extends TestCase diff --git a/tests/FavouriteTest.php b/tests/FavouriteTest.php index 03a712316..456f2213c 100644 --- a/tests/FavouriteTest.php +++ b/tests/FavouriteTest.php @@ -4,10 +4,6 @@ namespace Tests; use BookStack\Actions\Favourite; use BookStack\Auth\User; -use BookStack\Entities\Models\Book; -use BookStack\Entities\Models\Bookshelf; -use BookStack\Entities\Models\Chapter; -use BookStack\Entities\Models\Page; class FavouriteTest extends TestCase { @@ -83,16 +79,11 @@ class FavouriteTest extends TestCase ]); } - public function test_book_chapter_shelf_pages_contain_favourite_button() + public function test_each_entity_type_shows_favourite_button() { - $entities = [ - Bookshelf::query()->first(), - Book::query()->first(), - Chapter::query()->first(), - ]; $this->actingAs($this->getEditor()); - foreach ($entities as $entity) { + foreach ($this->entities->all() as $entity) { $resp = $this->get($entity->getUrl()); $this->withHtml($resp)->assertElementExists('form[method="POST"][action$="/favourites/add"]'); } diff --git a/tests/Helpers/EntityProvider.php b/tests/Helpers/EntityProvider.php index 152f7a3ac..05925909e 100644 --- a/tests/Helpers/EntityProvider.php +++ b/tests/Helpers/EntityProvider.php @@ -13,7 +13,13 @@ use BookStack\Entities\Repos\BookRepo; use BookStack\Entities\Repos\BookshelfRepo; use BookStack\Entities\Repos\ChapterRepo; use BookStack\Entities\Repos\PageRepo; +use Illuminate\Database\Eloquent\Builder; +/** + * Class to provider and action entity models for common test case + * operations. Tracks handled models and only returns fresh models. + * Does not dedupe against nested/child/parent models. + */ class EntityProvider { /** @@ -29,43 +35,68 @@ class EntityProvider /** * Get an un-fetched page from the system. */ - public function page(): Page + public function page(callable $queryFilter = null): Page { /** @var Page $page */ - $page = Page::query()->whereNotIn('id', $this->fetchCache['page'])->first(); + $page = Page::query()->when($queryFilter, $queryFilter)->whereNotIn('id', $this->fetchCache['page'])->first(); $this->addToCache($page); return $page; } + public function pageWithinChapter(): Page + { + return $this->page(fn(Builder $query) => $query->whereHas('chapter')->with('chapter')); + } + + public function pageNotWithinChapter(): Page + { + return $this->page(fn(Builder $query) => $query->where('chapter_id', '=', 0)); + } + /** * Get an un-fetched chapter from the system. */ - public function chapter(): Chapter + public function chapter(callable $queryFilter = null): Chapter { /** @var Chapter $chapter */ - $chapter = Chapter::query()->whereNotIn('id', $this->fetchCache['chapter'])->first(); + $chapter = Chapter::query()->when($queryFilter, $queryFilter)->whereNotIn('id', $this->fetchCache['chapter'])->first(); $this->addToCache($chapter); return $chapter; } + public function chapterHasPages(): Chapter + { + return $this->chapter(fn(Builder $query) => $query->whereHas('pages')); + } + /** * Get an un-fetched book from the system. */ - public function book(): Book + public function book(callable $queryFilter = null): Book { /** @var Book $book */ - $book = Book::query()->whereNotIn('id', $this->fetchCache['book'])->first(); + $book = Book::query()->when($queryFilter, $queryFilter)->whereNotIn('id', $this->fetchCache['book'])->first(); $this->addToCache($book); return $book; } + /** + * Get a book that has chapters and pages assigned. + */ + public function bookHasChaptersAndPages(): Book + { + return $this->book(function (Builder $query) { + $query->has('chapters')->has('pages')->with(['chapters', 'pages']); + }); + } + /** * Get an un-fetched shelf from the system. */ - public function shelf(): Bookshelf + public function shelf(callable $queryFilter = null): Bookshelf { /** @var Bookshelf $shelf */ - $shelf = Bookshelf::query()->whereNotIn('id', $this->fetchCache['bookshelf'])->first(); + $shelf = Bookshelf::query()->when($queryFilter, $queryFilter)->whereNotIn('id', $this->fetchCache['bookshelf'])->first(); $this->addToCache($shelf); return $shelf; } @@ -84,6 +115,12 @@ class EntityProvider ]; } + public function updatePage(Page $page, array $data): Page + { + $this->addToCache($page); + return app()->make(PageRepo::class)->update($page, $data); + } + /** * Create a book to page chain of entities that belong to a specific user. * @return array{book: Book, chapter: Chapter, page: Page} diff --git a/tests/TestServiceProvider.php b/tests/Helpers/TestServiceProvider.php similarity index 96% rename from tests/TestServiceProvider.php rename to tests/Helpers/TestServiceProvider.php index 9ad48c442..8b0e2ce16 100644 --- a/tests/TestServiceProvider.php +++ b/tests/Helpers/TestServiceProvider.php @@ -1,6 +1,6 @@ asEditor(); - /** @var Page $included */ - $included = Page::query()->first(); + $included = $this->entities->page(); $content = str_repeat('This is the body content of my custom homepage.', 20); $included->html = $content; $included->save(); @@ -138,7 +135,7 @@ class HomepageTest extends TestCase { $editor = $this->getEditor(); setting()->putUser($editor, 'bookshelves_view_type', 'grid'); - $shelf = Bookshelf::query()->firstOrFail(); + $shelf = $this->entities->shelf(); $this->setSettings(['app-homepage-type' => 'bookshelves']); diff --git a/tests/OpenGraphTest.php b/tests/OpenGraphTest.php index f3c439767..d6f535718 100644 --- a/tests/OpenGraphTest.php +++ b/tests/OpenGraphTest.php @@ -2,10 +2,6 @@ namespace Tests; -use BookStack\Entities\Models\Book; -use BookStack\Entities\Models\Bookshelf; -use BookStack\Entities\Models\Chapter; -use BookStack\Entities\Models\Page; use BookStack\Entities\Repos\BaseRepo; use BookStack\Entities\Repos\BookRepo; use Illuminate\Support\Str; diff --git a/tests/Permissions/EntityOwnerChangeTest.php b/tests/Permissions/EntityOwnerChangeTest.php index 65a67dc0f..e94759760 100644 --- a/tests/Permissions/EntityOwnerChangeTest.php +++ b/tests/Permissions/EntityOwnerChangeTest.php @@ -3,10 +3,6 @@ namespace Tests\Permissions; use BookStack\Auth\User; -use BookStack\Entities\Models\Book; -use BookStack\Entities\Models\Bookshelf; -use BookStack\Entities\Models\Chapter; -use BookStack\Entities\Models\Page; use Tests\TestCase; class EntityOwnerChangeTest extends TestCase diff --git a/tests/Permissions/EntityPermissionsTest.php b/tests/Permissions/EntityPermissionsTest.php index 9312b88cf..7f91e7887 100644 --- a/tests/Permissions/EntityPermissionsTest.php +++ b/tests/Permissions/EntityPermissionsTest.php @@ -13,15 +13,8 @@ use Tests\TestCase; class EntityPermissionsTest extends TestCase { - /** - * @var User - */ - protected $user; - - /** - * @var User - */ - protected $viewer; + protected User $user; + protected User $viewer; protected function setUp(): void { diff --git a/tests/Permissions/ExportPermissionsTest.php b/tests/Permissions/ExportPermissionsTest.php index 44f1a35cc..642cf1beb 100644 --- a/tests/Permissions/ExportPermissionsTest.php +++ b/tests/Permissions/ExportPermissionsTest.php @@ -2,8 +2,6 @@ namespace Tests\Permissions; -use BookStack\Entities\Models\Book; -use BookStack\Entities\Models\Chapter; use Illuminate\Support\Str; use Tests\TestCase; diff --git a/tests/Permissions/RolesTest.php b/tests/Permissions/RolesTest.php index 23bfde74c..7512c6d2f 100644 --- a/tests/Permissions/RolesTest.php +++ b/tests/Permissions/RolesTest.php @@ -17,7 +17,7 @@ use Tests\TestCase; class RolesTest extends TestCase { - protected $user; + protected User $user; protected function setUp(): void { diff --git a/tests/PublicActionTest.php b/tests/PublicActionTest.php index 14759c578..7e3f7be00 100644 --- a/tests/PublicActionTest.php +++ b/tests/PublicActionTest.php @@ -8,7 +8,6 @@ use BookStack\Auth\Role; use BookStack\Auth\User; use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Chapter; -use BookStack\Entities\Models\Page; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\View; diff --git a/tests/References/CrossLinkParserTest.php b/tests/References/CrossLinkParserTest.php index eb862a9fd..65eed9e70 100644 --- a/tests/References/CrossLinkParserTest.php +++ b/tests/References/CrossLinkParserTest.php @@ -2,7 +2,6 @@ namespace Tests\References; -use BookStack\Entities\Models\Page; use BookStack\References\CrossLinkParser; use Tests\TestCase; @@ -11,7 +10,7 @@ class CrossLinkParserTest extends TestCase public function test_instance_with_entity_resolvers_matches_entity_links() { $entities = $this->entities->all(); - $otherPage = Page::query()->where('id', '!=', $entities['page']->id)->first(); + $otherPage = $this->entities->page(); $html = ' Page Permalink diff --git a/tests/References/ReferencesTest.php b/tests/References/ReferencesTest.php index 16ea19ac5..148b2197c 100644 --- a/tests/References/ReferencesTest.php +++ b/tests/References/ReferencesTest.php @@ -2,9 +2,6 @@ namespace Tests\References; -use BookStack\Entities\Models\Book; -use BookStack\Entities\Models\Chapter; -use BookStack\Entities\Models\Page; use BookStack\Entities\Repos\PageRepo; use BookStack\Entities\Tools\TrashCan; use BookStack\Model; @@ -15,10 +12,8 @@ class ReferencesTest extends TestCase { public function test_references_created_on_page_update() { - /** @var Page $pageA */ - /** @var Page $pageB */ - $pageA = Page::query()->first(); - $pageB = Page::query()->where('id', '!=', $pageA->id)->first(); + $pageA = $this->entities->page(); + $pageB = $this->entities->page(); $this->assertDatabaseMissing('references', ['from_id' => $pageA->id, 'from_type' => $pageA->getMorphClass()]); @@ -37,10 +32,8 @@ class ReferencesTest extends TestCase public function test_references_deleted_on_entity_delete() { - /** @var Page $pageA */ - /** @var Page $pageB */ - $pageA = Page::query()->first(); - $pageB = Page::query()->where('id', '!=', $pageA->id)->first(); + $pageA = $this->entities->page(); + $pageB = $this->entities->page(); $this->createReference($pageA, $pageB); $this->createReference($pageB, $pageA); @@ -58,8 +51,7 @@ class ReferencesTest extends TestCase public function test_references_to_count_visible_on_entity_show_view() { $entities = $this->entities->all(); - /** @var Page $otherPage */ - $otherPage = Page::query()->where('id', '!=', $entities['page']->id)->first(); + $otherPage = $this->entities->page(); $this->asEditor(); foreach ($entities as $entity) { @@ -95,10 +87,8 @@ class ReferencesTest extends TestCase public function test_reference_not_visible_if_view_permission_does_not_permit() { - /** @var Page $page */ - /** @var Page $pageB */ $page = $this->entities->page(); - $pageB = Page::query()->where('id', '!=', $page->id)->first(); + $pageB = $this->entities->page(); $this->createReference($pageB, $page); $this->entities->setPermissions($pageB); @@ -118,11 +108,8 @@ class ReferencesTest extends TestCase public function test_pages_leading_to_entity_updated_on_url_change() { - /** @var Page $pageA */ - /** @var Page $pageB */ - /** @var Book $book */ - $pageA = Page::query()->first(); - $pageB = Page::query()->where('id', '!=', $pageA->id)->first(); + $pageA = $this->entities->page(); + $pageB = $this->entities->page(); $book = $this->entities->book(); foreach ([$pageA, $pageB] as $page) { @@ -147,11 +134,8 @@ class ReferencesTest extends TestCase public function test_pages_linking_to_other_page_updated_on_parent_book_url_change() { - /** @var Page $bookPage */ - /** @var Page $otherPage */ - /** @var Book $book */ - $bookPage = Page::query()->first(); - $otherPage = Page::query()->where('id', '!=', $bookPage->id)->first(); + $bookPage = $this->entities->page(); + $otherPage = $this->entities->page(); $book = $bookPage->book; $otherPage->html = 'Link'; @@ -172,11 +156,8 @@ class ReferencesTest extends TestCase public function test_pages_linking_to_chapter_updated_on_parent_book_url_change() { - /** @var Chapter $bookChapter */ - /** @var Page $otherPage */ - /** @var Book $book */ - $bookChapter = Chapter::query()->first(); - $otherPage = Page::query()->first(); + $bookChapter = $this->entities->chapter(); + $otherPage = $this->entities->page(); $book = $bookChapter->book; $otherPage->html = 'Link'; @@ -197,8 +178,6 @@ class ReferencesTest extends TestCase public function test_markdown_links_leading_to_entity_updated_on_url_change() { - /** @var Page $page */ - /** @var Book $book */ $page = $this->entities->page(); $book = $this->entities->book(); diff --git a/tests/Settings/RecycleBinTest.php b/tests/Settings/RecycleBinTest.php index 8b5705afd..3d27e9c8d 100644 --- a/tests/Settings/RecycleBinTest.php +++ b/tests/Settings/RecycleBinTest.php @@ -3,10 +3,7 @@ namespace Tests\Settings; use BookStack\Entities\Models\Book; -use BookStack\Entities\Models\Bookshelf; -use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Deletion; -use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\Page; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\DB; @@ -97,7 +94,7 @@ class RecycleBinTest extends TestCase public function test_entity_restore() { - $book = Book::query()->whereHas('pages')->whereHas('chapters')->with(['pages', 'chapters'])->firstOrFail(); + $book = $this->entities->bookHasChaptersAndPages(); $this->asEditor()->delete($book->getUrl()); $deletion = Deletion::query()->firstOrFail(); @@ -118,7 +115,7 @@ class RecycleBinTest extends TestCase public function test_permanent_delete() { - $book = Book::query()->whereHas('pages')->whereHas('chapters')->with(['pages', 'chapters'])->firstOrFail(); + $book = $this->entities->bookHasChaptersAndPages(); $this->asEditor()->delete($book->getUrl()); $deletion = Deletion::query()->firstOrFail(); @@ -137,9 +134,7 @@ class RecycleBinTest extends TestCase public function test_permanent_delete_for_each_type() { - /** @var Entity $entity */ - foreach ([new Bookshelf(), new Book(), new Chapter(), new Page()] as $entity) { - $entity = $entity->newQuery()->first(); + foreach ($this->entities->all() as $type => $entity) { $this->asEditor()->delete($entity->getUrl()); $deletion = Deletion::query()->orderBy('id', 'desc')->firstOrFail(); @@ -152,7 +147,7 @@ class RecycleBinTest extends TestCase public function test_permanent_entity_delete_updates_existing_activity_with_entity_name() { - $page = Page::query()->firstOrFail(); + $page = $this->entities->page(); $this->asEditor()->delete($page->getUrl()); $deletion = $page->deletions()->firstOrFail(); @@ -181,8 +176,8 @@ class RecycleBinTest extends TestCase public function test_auto_clear_functionality_works() { config()->set('app.recycle_bin_lifetime', 5); - $page = Page::query()->firstOrFail(); - $otherPage = Page::query()->where('id', '!=', $page->id)->firstOrFail(); + $page = $this->entities->page(); + $otherPage = $this->entities->page(); $this->asEditor()->delete($page->getUrl()); $this->assertDatabaseHas('pages', ['id' => $page->id]); @@ -198,8 +193,8 @@ class RecycleBinTest extends TestCase public function test_auto_clear_functionality_with_negative_time_keeps_forever() { config()->set('app.recycle_bin_lifetime', -1); - $page = Page::query()->firstOrFail(); - $otherPage = Page::query()->where('id', '!=', $page->id)->firstOrFail(); + $page = $this->entities->page(); + $otherPage = $this->entities->page(); $this->asEditor()->delete($page->getUrl()); $this->assertEquals(1, Deletion::query()->count()); @@ -214,7 +209,7 @@ class RecycleBinTest extends TestCase public function test_auto_clear_functionality_with_zero_time_deletes_instantly() { config()->set('app.recycle_bin_lifetime', 0); - $page = Page::query()->firstOrFail(); + $page = $this->entities->page(); $this->asEditor()->delete($page->getUrl()); $this->assertDatabaseMissing('pages', ['id' => $page->id]); @@ -253,8 +248,7 @@ class RecycleBinTest extends TestCase public function test_restore_page_shows_link_to_parent_restore_if_parent_also_deleted() { - /** @var Book $book */ - $book = Book::query()->whereHas('pages')->whereHas('chapters')->with(['pages', 'chapters'])->firstOrFail(); + $book = $this->entities->bookHasChaptersAndPages(); $chapter = $book->chapters->first(); /** @var Page $page */ $page = $chapter->pages->first(); diff --git a/tests/TestCase.php b/tests/TestCase.php index cc8e57453..356fdaa37 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -27,6 +27,7 @@ use Monolog\Logger; use Psr\Http\Client\ClientInterface; use Ssddanbrown\AssertHtml\TestsHtml; use Tests\Helpers\EntityProvider; +use Tests\Helpers\TestServiceProvider; abstract class TestCase extends BaseTestCase { diff --git a/tests/UrlTest.php b/tests/UrlTest.php index dd278c240..c1e133804 100644 --- a/tests/UrlTest.php +++ b/tests/UrlTest.php @@ -4,9 +4,6 @@ namespace Tests; use BookStack\Http\Request; -use function request; -use function url; - class UrlTest extends TestCase { public function test_url_helper_takes_custom_url_into_account() diff --git a/tests/User/UserManagementTest.php b/tests/User/UserManagementTest.php index 114088338..e295034ce 100644 --- a/tests/User/UserManagementTest.php +++ b/tests/User/UserManagementTest.php @@ -6,7 +6,6 @@ use BookStack\Actions\ActivityType; use BookStack\Auth\Access\UserInviteService; use BookStack\Auth\Role; use BookStack\Auth\User; -use BookStack\Entities\Models\Page; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Str; use Mockery\MockInterface; diff --git a/tests/User/UserPreferencesTest.php b/tests/User/UserPreferencesTest.php index 2273be2b4..c65b11d7d 100644 --- a/tests/User/UserPreferencesTest.php +++ b/tests/User/UserPreferencesTest.php @@ -2,8 +2,6 @@ namespace Tests\User; -use BookStack\Entities\Models\Bookshelf; -use BookStack\Entities\Models\Page; use Tests\TestCase; class UserPreferencesTest extends TestCase From 953402f2eb05e00f2aa8440084df17a16fc8e52a Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 30 Sep 2022 18:26:58 +0100 Subject: [PATCH 23/55] Started playing with table icons To make a little more accessible, Related to #3397 --- resources/js/wysiwyg/config.js | 2 ++ resources/js/wysiwyg/icons.js | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 resources/js/wysiwyg/icons.js diff --git a/resources/js/wysiwyg/config.js b/resources/js/wysiwyg/config.js index 8c85f60e2..acf5e1d52 100644 --- a/resources/js/wysiwyg/config.js +++ b/resources/js/wysiwyg/config.js @@ -3,6 +3,7 @@ import {listen as listenForCommonEvents} from "./common-events"; import {scrollToQueryString} from "./scrolling"; import {listenForDragAndPaste} from "./drop-paste-handling"; import {getPrimaryToolbar, registerAdditionalToolbars} from "./toolbars"; +import {registerCustomIcons} from "./icons"; import {getPlugin as getCodeeditorPlugin} from "./plugin-codeeditor"; import {getPlugin as getDrawioPlugin} from "./plugin-drawio"; @@ -291,6 +292,7 @@ export function build(options) { head.innerHTML += fetchCustomHeadContent(); }, setup(editor) { + registerCustomIcons(editor); registerAdditionalToolbars(editor, options); getSetupCallback(options)(editor); }, diff --git a/resources/js/wysiwyg/icons.js b/resources/js/wysiwyg/icons.js new file mode 100644 index 000000000..2c2457fe1 --- /dev/null +++ b/resources/js/wysiwyg/icons.js @@ -0,0 +1,21 @@ +const icons = { + 'table-delete-column': '', + 'table-delete-row': '', + 'table-insert-column-after': '', + 'table-insert-column-before': '', + 'table-insert-row-above': '', + 'table-insert-row-after': '', + 'table': '', + 'table-delete-table': '', +}; + + +/** + * @param {Editor} editor + */ +export function registerCustomIcons(editor) { + + for (const [name, svg] of Object.entries(icons)) { + editor.ui.registry.addIcon(name, svg); + } +} \ No newline at end of file From f19bad89033ee31e9157341214ae3a7e3b0fbb40 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 2 Oct 2022 13:17:28 +0100 Subject: [PATCH 24/55] Started item permission design revamp --- resources/icons/role.svg | 4 +++ resources/sass/_components.scss | 31 ++++++++++++++++--- resources/sass/_layout.scss | 4 +-- resources/sass/_spacing.scss | 14 ++++++++- .../form/entity-permissions-row.blade.php | 28 +++++++++++++++++ .../views/form/entity-permissions.blade.php | 25 ++------------- 6 files changed, 77 insertions(+), 29 deletions(-) create mode 100644 resources/icons/role.svg create mode 100644 resources/views/form/entity-permissions-row.blade.php diff --git a/resources/icons/role.svg b/resources/icons/role.svg new file mode 100644 index 000000000..e7cad506d --- /dev/null +++ b/resources/icons/role.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/resources/sass/_components.scss b/resources/sass/_components.scss index c00f57954..9f737f3be 100644 --- a/resources/sass/_components.scss +++ b/resources/sass/_components.scss @@ -798,11 +798,34 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group { max-width: 500px; } -.permissions-table [permissions-table-toggle-all-in-row] { - display: none; +.content-permissions { + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1); } -.permissions-table tr:hover [permissions-table-toggle-all-in-row] { - display: inline; +.content-permissions-row { + border: 1.5px solid #E2E2E2; + border-bottom-width: 0; + label { + padding-bottom: 0; + } + &:hover { + background-color: #F2F2F2; + } +} +.content-permissions-row:first-child { + border-radius: 4px 4px 0 0; +} +.content-permissions-row:last-child { + border-radius: 0 0 4px 4px; + border-bottom-width: 1.5px; +} +.content-permissions-row-toggle-all { + visibility: hidden; +} +.content-permissions-row:hover .content-permissions-row-toggle-all { + visibility: visible; +} +.content-permissions-row-label { + font-weight: bold; } .template-item { diff --git a/resources/sass/_layout.scss b/resources/sass/_layout.scss index 2cd57d496..cfb8397c9 100644 --- a/resources/sass/_layout.scss +++ b/resources/sass/_layout.scss @@ -158,8 +158,8 @@ body.flexbox { } } -.gap-m { - gap: $-m; +.flex-none { + flex: none; } .justify-flex-start { diff --git a/resources/sass/_spacing.scss b/resources/sass/_spacing.scss index 40217de9b..14f8918dc 100644 --- a/resources/sass/_spacing.scss +++ b/resources/sass/_spacing.scss @@ -29,4 +29,16 @@ } } @include spacing('margin', 'm'); -@include spacing('padding', 'p'); \ No newline at end of file +@include spacing('padding', 'p'); + +@each $sizeLetter, $size in $spacing { + .gap-#{$sizeLetter} { + gap: $size !important; + } + .gap-x-#{$sizeLetter} { + column-gap: $size !important; + } + .gap-y-#{$sizeLetter} { + row-gap: $size !important; + } +} diff --git a/resources/views/form/entity-permissions-row.blade.php b/resources/views/form/entity-permissions-row.blade.php new file mode 100644 index 000000000..023aa36d2 --- /dev/null +++ b/resources/views/form/entity-permissions-row.blade.php @@ -0,0 +1,28 @@ +
+
+
+ @icon('role') +
+ {{ $role->display_name }} + +
+
+
+ @include('form.restriction-checkbox', ['name'=>'restrictions', 'label' => trans('common.view'), 'action' => 'view']) +
+
+ @if(!$model->isA('page')) + @include('form.restriction-checkbox', ['name'=>'restrictions', 'label' => trans('common.create'), 'action' => 'create']) + @endif +
+
+ @include('form.restriction-checkbox', ['name'=>'restrictions', 'label' => trans('common.update'), 'action' => 'update']) +
+
+ @include('form.restriction-checkbox', ['name'=>'restrictions', 'label' => trans('common.delete'), 'action' => 'delete']) +
+
+
\ No newline at end of file diff --git a/resources/views/form/entity-permissions.blade.php b/resources/views/form/entity-permissions.blade.php index 206955fe9..18df0bb69 100644 --- a/resources/views/form/entity-permissions.blade.php +++ b/resources/views/form/entity-permissions.blade.php @@ -24,31 +24,12 @@

{{ trans('entities.shelves_permissions_cascade_warning') }}

@endif -
- - - - - +
@foreach(\BookStack\Auth\Role::restrictable() as $role) -
- - - @if(!$model->isA('page')) - - @endif - - - + @include('form.entity-permissions-row', ['role' => $role, 'model' => $model]) @endforeach -
{{ trans('common.role') }} - {{ trans('common.actions') }} - {{ trans('common.toggle_all') }} -
- {{ $role->display_name }} - {{ trans('common.toggle_all') }} - @include('form.restriction-checkbox', ['name'=>'restrictions', 'label' => trans('common.view'), 'action' => 'view'])@include('form.restriction-checkbox', ['name'=>'restrictions', 'label' => trans('common.create'), 'action' => 'create'])@include('form.restriction-checkbox', ['name'=>'restrictions', 'label' => trans('common.update'), 'action' => 'update'])@include('form.restriction-checkbox', ['name'=>'restrictions', 'label' => trans('common.delete'), 'action' => 'delete'])
+
{{ trans('common.cancel') }} From b8b0afa0df6f9a63438c214fc08664e6b4cd3455 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 2 Oct 2022 13:57:32 +0100 Subject: [PATCH 25/55] Cleaned up old permission JS code Removed now unused JS entity-permissions compontent. Updated existing permissions-table compontent to newer format. Removed now unused translation string. --- .../components/entity-permissions-editor.js | 20 ------------- resources/js/components/index.js | 2 -- resources/js/components/permissions-table.js | 15 +++++----- resources/lang/en/entities.php | 1 - .../form/entity-permissions-row.blade.php | 6 ++-- .../views/form/entity-permissions.blade.php | 9 +----- .../views/settings/roles/parts/form.blade.php | 30 +++++++++---------- 7 files changed, 26 insertions(+), 57 deletions(-) delete mode 100644 resources/js/components/entity-permissions-editor.js diff --git a/resources/js/components/entity-permissions-editor.js b/resources/js/components/entity-permissions-editor.js deleted file mode 100644 index a821792a0..000000000 --- a/resources/js/components/entity-permissions-editor.js +++ /dev/null @@ -1,20 +0,0 @@ - -class EntityPermissionsEditor { - - constructor(elem) { - this.permissionsTable = elem.querySelector('[permissions-table]'); - - // Handle toggle all event - this.restrictedCheckbox = elem.querySelector('[name=restricted]'); - this.restrictedCheckbox.addEventListener('change', this.updateTableVisibility.bind(this)); - } - - updateTableVisibility() { - this.permissionsTable.style.display = - this.restrictedCheckbox.checked - ? null - : 'none'; - } -} - -export default EntityPermissionsEditor; \ No newline at end of file diff --git a/resources/js/components/index.js b/resources/js/components/index.js index f360e2b0c..4b17dc403 100644 --- a/resources/js/components/index.js +++ b/resources/js/components/index.js @@ -18,7 +18,6 @@ import dropdown from "./dropdown.js" import dropdownSearch from "./dropdown-search.js" import dropzone from "./dropzone.js" import editorToolbox from "./editor-toolbox.js" -import entityPermissionsEditor from "./entity-permissions-editor.js" import entitySearch from "./entity-search.js" import entitySelector from "./entity-selector.js" import entitySelectorPopup from "./entity-selector-popup.js" @@ -75,7 +74,6 @@ const componentMapping = { "dropdown-search": dropdownSearch, "dropzone": dropzone, "editor-toolbox": editorToolbox, - "entity-permissions-editor": entityPermissionsEditor, "entity-search": entitySearch, "entity-selector": entitySelector, "entity-selector-popup": entitySelectorPopup, diff --git a/resources/js/components/permissions-table.js b/resources/js/components/permissions-table.js index baad75258..df3c055ca 100644 --- a/resources/js/components/permissions-table.js +++ b/resources/js/components/permissions-table.js @@ -1,22 +1,21 @@ class PermissionsTable { - constructor(elem) { - this.container = elem; + setup() { + this.container = this.$el; // Handle toggle all event - const toggleAll = elem.querySelector('[permissions-table-toggle-all]'); - toggleAll.addEventListener('click', this.toggleAllClick.bind(this)); + for (const toggleAllElem of (this.$manyRefs.toggleAll || [])) { + toggleAllElem.addEventListener('click', this.toggleAllClick.bind(this)); + } // Handle toggle row event - const toggleRowElems = elem.querySelectorAll('[permissions-table-toggle-all-in-row]'); - for (let toggleRowElem of toggleRowElems) { + for (const toggleRowElem of (this.$manyRefs.toggleRow || [])) { toggleRowElem.addEventListener('click', this.toggleRowClick.bind(this)); } // Handle toggle column event - const toggleColumnElems = elem.querySelectorAll('[permissions-table-toggle-all-in-column]'); - for (let toggleColElem of toggleColumnElems) { + for (const toggleColElem of (this.$manyRefs.toggleColumn || [])) { toggleColElem.addEventListener('click', this.toggleColumnClick.bind(this)); } } diff --git a/resources/lang/en/entities.php b/resources/lang/en/entities.php index 1720801d2..28ec591d7 100644 --- a/resources/lang/en/entities.php +++ b/resources/lang/en/entities.php @@ -43,7 +43,6 @@ return [ // Permissions and restrictions 'permissions' => 'Permissions', 'permissions_intro' => 'Once enabled, These permissions will take priority over any set role permissions.', - 'permissions_enable' => 'Enable Custom Permissions', 'permissions_save' => 'Save Permissions', 'permissions_owner' => 'Owner', diff --git a/resources/views/form/entity-permissions-row.blade.php b/resources/views/form/entity-permissions-row.blade.php index 023aa36d2..bff7315a0 100644 --- a/resources/views/form/entity-permissions-row.blade.php +++ b/resources/views/form/entity-permissions-row.blade.php @@ -1,4 +1,4 @@ -
+
@icon('role') @@ -6,7 +6,7 @@ {{ $role->display_name }}
@@ -14,7 +14,7 @@ @include('form.restriction-checkbox', ['name'=>'restrictions', 'label' => trans('common.view'), 'action' => 'view'])
- @if(!$model->isA('page')) + @if(!$model instanceof \BookStack\Entities\Models\Page) @include('form.restriction-checkbox', ['name'=>'restrictions', 'label' => trans('common.create'), 'action' => 'create']) @endif
diff --git a/resources/views/form/entity-permissions.blade.php b/resources/views/form/entity-permissions.blade.php index 18df0bb69..321e2f06c 100644 --- a/resources/views/form/entity-permissions.blade.php +++ b/resources/views/form/entity-permissions.blade.php @@ -1,16 +1,10 @@ -
+ {!! csrf_field() !!}

{{ trans('entities.permissions_intro') }}

-
- @include('form.checkbox', [ - 'name' => 'restricted', - 'label' => trans('entities.permissions_enable'), - ]) -
@@ -24,7 +18,6 @@

{{ trans('entities.shelves_permissions_cascade_warning') }}

@endif -
@foreach(\BookStack\Auth\Role::restrictable() as $role) @include('form.entity-permissions-row', ['role' => $role, 'model' => $model]) diff --git a/resources/views/settings/roles/parts/form.blade.php b/resources/views/settings/roles/parts/form.blade.php index 593791997..044b4ceb4 100644 --- a/resources/views/settings/roles/parts/form.blade.php +++ b/resources/views/settings/roles/parts/form.blade.php @@ -26,9 +26,9 @@
-
+
- {{ trans('common.toggle_all') }} + {{ trans('common.toggle_all') }}
@@ -56,20 +56,20 @@

{{ trans('settings.role_asset_admins') }}

@endif - +
- - - - + + + + @@ -187,7 +187,7 @@ @@ -205,7 +205,7 @@ From a090720241ed3a0471a4dc9860b9f7cf8837f562 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 2 Oct 2022 14:27:12 +0100 Subject: [PATCH 26/55] Developed dev JS docs a bit further --- dev/docs/components.md | 99 -------------------------- dev/docs/development.md | 2 + dev/docs/javascript-code.md | 138 ++++++++++++++++++++++++++++++++++++ 3 files changed, 140 insertions(+), 99 deletions(-) delete mode 100644 dev/docs/components.md create mode 100644 dev/docs/javascript-code.md diff --git a/dev/docs/components.md b/dev/docs/components.md deleted file mode 100644 index 832765dd6..000000000 --- a/dev/docs/components.md +++ /dev/null @@ -1,99 +0,0 @@ -# JavaScript Components - -This document details the format for JavaScript components in BookStack. This is a really simple class-based setup with a few helpers provided. - -#### Defining a Component in JS - -```js -class Dropdown { - setup() { - this.toggle = this.$refs.toggle; - this.menu = this.$refs.menu; - - this.speed = parseInt(this.$opts.speed); - } -} -``` - -All usage of $refs, $manyRefs and $opts should be done at the top of the `setup` function so any requirements can be easily seen. - -#### Using a Component in HTML - -A component is used like so: - -```html -
- - - -
-``` - -The names will be parsed and new component instance will be created if a matching name is found in the `components/index.js` componentMapping. - -#### Element References - -Within a component you'll often need to refer to other element instances. This can be done like so: - -```html -
- View more -
-``` - -You can then access the span element as `this.$refs.toggle` in your component. - -#### Component Options - -```html -
-
-``` - -Will result with `this.$opts` being: - -```json -{ - "delay": "500", - "show": "" -} -``` - -#### Global Helpers - -There are various global helper libraries which can be used in components: - -```js -// HTTP service -window.$http.get(url, params); -window.$http.post(url, data); -window.$http.put(url, data); -window.$http.delete(url, data); -window.$http.patch(url, data); - -// Global event system -// Emit a global event -window.$events.emit(eventName, eventData); -// Listen to a global event -window.$events.listen(eventName, callback); -// Show a success message -window.$events.success(message); -// Show an error message -window.$events.error(message); -// Show validation errors, if existing, as an error notification -window.$events.showValidationErrors(error); - -// Translator -// Take the given plural text and count to decide on what plural option -// to use, Similar to laravel's trans_choice function but instead -// takes the direction directly instead of a translation key. -window.trans_plural(translationString, count, replacements); - -// Component System -// Parse and initialise any components from the given root el down. -window.components.init(rootEl); -// Get the first active component of the given name -window.components.first(name); -``` \ No newline at end of file diff --git a/dev/docs/development.md b/dev/docs/development.md index 6d11443b6..1611de578 100644 --- a/dev/docs/development.md +++ b/dev/docs/development.md @@ -5,6 +5,8 @@ When it's time for a release the `development` branch is merged into release wit * [Node.js](https://nodejs.org/en/) v16.0+ +## Building CSS & JavaScript Assets + This project uses SASS for CSS development and this is built, along with the JavaScript, using a range of npm scripts. The below npm commands can be used to install the dependencies & run the build tasks: ``` bash diff --git a/dev/docs/javascript-code.md b/dev/docs/javascript-code.md new file mode 100644 index 000000000..3d47a1ad8 --- /dev/null +++ b/dev/docs/javascript-code.md @@ -0,0 +1,138 @@ +# BookStack JavaScript Code + +BookStack is primarily server-side-rendered, but it uses JavaScript sparingly to drive any required dynamic elements. Most JavaScript is applied via a custom, and very thin, component interface to keep code organised and somewhat reusable. + +JavaScript source code can be found in the `resources/js` directory. This gets bundled and transformed by `esbuild`, ending up in the `public/dist` folder for browser use. Read the [Development > "Building CSS & JavaScript Assets"](development.md#building-css-&-javascript-assets) documentation for details on this process. + +## Components + +This section details the format for JavaScript components in BookStack. This is a really simple class-based setup with a few helpers provided. + +### Defining a Component in JS + +```js +class Dropdown { + setup() { + this.container = this.$el; + this.menu = this.$refs.menu; + this.toggles = this.$manyRefs.toggle; + + this.speed = parseInt(this.$opts.speed); + } +} +``` + +All usage of $refs, $manyRefs and $opts should be done at the top of the `setup` function so any requirements can be easily seen. + +Once defined, the component has to be registered for use. This is done in the `resources/js/components/index.js` file. You'll need to import the component class then add it to `componentMapping` object, following the pattern of other components. + +### Using a Component in HTML + +A component is used like so: + +```html +
+ + + +
+``` + +The names will be parsed and new component instance will be created if a matching name is found in the `components/index.js` componentMapping. + +### Element References + +Within a component you'll often need to refer to other element instances. This can be done like so: + +```html +
+ View more +
+``` + +You can then access the span element as `this.$refs.toggle` in your component. + +Multiple elements of the same reference name can be accessed via a `this.$manyRefs` property within your component. For example, all the buttons in the below example could be accessed via `this.$manyRefs.buttons`. + +```html +
+ + + +
+``` + +### Component Options + +```html +
+
+``` + +Will result with `this.$opts` being: + +```json +{ + "delay": "500", + "show": "" +} +``` + +#### Component Properties + +A component has the below shown properties available for use. As mentioned above, most of these should be used within the `setup()` function to make the requirements/dependencies of the component clear. + +```javascript +// The root element that the compontent has been applied to. +this.$el + +// A map of defined element references within the compontent. +// See "Element References" above. +this.$refs + +// A map of defined multi-element references within the compontent. +// See "Element References" above. +this.$manyRefs + +// Options defined for the compontent. +this.$opts +``` + +## Global JavaScript Helpers + +There are various global helper libraries in BookStack which can be accessed via the `window`. The below provides an overview of what's available. + +```js +// HTTP service +window.$http.get(url, params); +window.$http.post(url, data); +window.$http.put(url, data); +window.$http.delete(url, data); +window.$http.patch(url, data); + +// Global event system +// Emit a global event +window.$events.emit(eventName, eventData); +// Listen to a global event +window.$events.listen(eventName, callback); +// Show a success message +window.$events.success(message); +// Show an error message +window.$events.error(message); +// Show validation errors, if existing, as an error notification +window.$events.showValidationErrors(error); + +// Translator +// Take the given plural text and count to decide on what plural option +// to use, Similar to laravel's trans_choice function but instead +// takes the direction directly instead of a translation key. +window.trans_plural(translationString, count, replacements); + +// Component System +// Parse and initialise any components from the given root el down. +window.components.init(rootEl); +// Get the first active component of the given name +window.components.first(name); +``` \ No newline at end of file From a03245e427d3257eeb2bbf137e8e6ce1388c1e69 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 2 Oct 2022 18:09:48 +0100 Subject: [PATCH 27/55] Added user-interface for "Everyone Else" entity permission item Nothing on back-end logic done to hook this new option up. Addition of permissions for role_id=0 works out of the box, but active "everyone else" permissions, with no priviliges, is currently not working. Needs change of permission gen logic also. --- app/Auth/Role.php | 13 +++++++ app/Entities/Models/Entity.php | 6 ++- resources/icons/groups.svg | 1 + resources/js/components/entity-permissions.js | 24 ++++++++++++ resources/js/components/index.js | 2 + resources/sass/_components.scss | 3 -- resources/sass/_forms.scss | 9 +++++ .../views/form/custom-checkbox.blade.php | 2 +- .../form/entity-permissions-row.blade.php | 38 ++++++++++++++----- .../views/form/entity-permissions.blade.php | 6 ++- 10 files changed, 87 insertions(+), 17 deletions(-) create mode 100644 resources/icons/groups.svg create mode 100644 resources/js/components/entity-permissions.js diff --git a/app/Auth/Role.php b/app/Auth/Role.php index 51b2ce301..3ae469b59 100644 --- a/app/Auth/Role.php +++ b/app/Auth/Role.php @@ -120,6 +120,19 @@ class Role extends Model implements Loggable ->get(); } + /** + * Get a role to represent the case of 'Everyone else' in the system. + * Used within the interface since the default-fallback for permissions uses role_id=0. + */ + public static function getEveryoneElseRole(): self + { + return (new static())->forceFill([ + 'id' => 0, + 'display_name' => 'Everyone Else', + 'description' => 'Set permissions for all roles not specifically overridden.' + ]); + } + /** * {@inheritdoc} */ diff --git a/app/Entities/Models/Entity.php b/app/Entities/Models/Entity.php index 26a52073e..3528eaf2b 100644 --- a/app/Entities/Models/Entity.php +++ b/app/Entities/Models/Entity.php @@ -184,8 +184,10 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable */ public function hasRestriction(int $role_id, string $action): bool { - return $this->permissions()->where('role_id', '=', $role_id) - ->where('action', '=', $action)->count() > 0; + return $this->permissions() + ->where('role_id', '=', $role_id) + ->where('action', '=', $action) + ->count() > 0; } /** diff --git a/resources/icons/groups.svg b/resources/icons/groups.svg new file mode 100644 index 000000000..c99a6b503 --- /dev/null +++ b/resources/icons/groups.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/js/components/entity-permissions.js b/resources/js/components/entity-permissions.js new file mode 100644 index 000000000..8b57d3376 --- /dev/null +++ b/resources/js/components/entity-permissions.js @@ -0,0 +1,24 @@ + + +class EntityPermissions { + + setup() { + this.everyoneInheritToggle = this.$refs.everyoneInherit; + + this.setupListeners(); + } + + setupListeners() { + this.everyoneInheritToggle.addEventListener('change', event => { + const inherit = event.target.checked; + const permissions = document.querySelectorAll('input[type="checkbox"][name^="restrictions[0]["]'); + for (const permission of permissions) { + permission.disabled = inherit; + permission.checked = false; + } + }) + } + +} + +export default EntityPermissions; \ No newline at end of file diff --git a/resources/js/components/index.js b/resources/js/components/index.js index 4b17dc403..7d00cb671 100644 --- a/resources/js/components/index.js +++ b/resources/js/components/index.js @@ -18,6 +18,7 @@ import dropdown from "./dropdown.js" import dropdownSearch from "./dropdown-search.js" import dropzone from "./dropzone.js" import editorToolbox from "./editor-toolbox.js" +import entityPermissions from "./entity-permissions"; import entitySearch from "./entity-search.js" import entitySelector from "./entity-selector.js" import entitySelectorPopup from "./entity-selector-popup.js" @@ -74,6 +75,7 @@ const componentMapping = { "dropdown-search": dropdownSearch, "dropzone": dropzone, "editor-toolbox": editorToolbox, + "entity-permissions": entityPermissions, "entity-search": entitySearch, "entity-selector": entitySelector, "entity-selector-popup": entitySelectorPopup, diff --git a/resources/sass/_components.scss b/resources/sass/_components.scss index 9f737f3be..d0aadce6e 100644 --- a/resources/sass/_components.scss +++ b/resources/sass/_components.scss @@ -824,9 +824,6 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group { .content-permissions-row:hover .content-permissions-row-toggle-all { visibility: visible; } -.content-permissions-row-label { - font-weight: bold; -} .template-item { cursor: pointer; diff --git a/resources/sass/_forms.scss b/resources/sass/_forms.scss index 7025aa898..5c1c8b2e8 100644 --- a/resources/sass/_forms.scss +++ b/resources/sass/_forms.scss @@ -266,6 +266,15 @@ input[type=color] { background-color: rgba(0, 0, 0, 0.05); opacity: 0.8; } + input[type=checkbox][disabled] ~ * { + opacity: 0.8; + cursor: not-allowed; + } + input[type=checkbox][disabled] ~ .custom-checkbox { + border-color: #999; + color: #999 !important; + background: #f2f2f2; + } } .toggle-switch-list { .toggle-switch { diff --git a/resources/views/form/custom-checkbox.blade.php b/resources/views/form/custom-checkbox.blade.php index 2bf4e2232..de3ffe922 100644 --- a/resources/views/form/custom-checkbox.blade.php +++ b/resources/views/form/custom-checkbox.blade.php @@ -5,7 +5,7 @@ $checked $label --}}
- {{ trans('common.toggle_all') }} + {{ trans('common.toggle_all') }} {{ trans('common.create') }}{{ trans('common.view') }}{{ trans('common.edit') }}{{ trans('common.delete') }}{{ trans('common.create') }}{{ trans('common.view') }}{{ trans('common.edit') }}{{ trans('common.delete') }}
{{ trans('entities.shelves') }}
- {{ trans('common.toggle_all') }} + {{ trans('common.toggle_all') }}
@include('settings.roles.parts.checkbox', ['permission' => 'bookshelf-create-all', 'label' => trans('settings.role_all')]) @@ -93,7 +93,7 @@
{{ trans('entities.books') }}
- {{ trans('common.toggle_all') }} + {{ trans('common.toggle_all') }}
@include('settings.roles.parts.checkbox', ['permission' => 'book-create-all', 'label' => trans('settings.role_all')]) @@ -117,7 +117,7 @@
{{ trans('entities.chapters') }}
- {{ trans('common.toggle_all') }} + {{ trans('common.toggle_all') }}
@include('settings.roles.parts.checkbox', ['permission' => 'chapter-create-own', 'label' => trans('settings.role_own')]) @@ -143,7 +143,7 @@
{{ trans('entities.pages') }}
- {{ trans('common.toggle_all') }} + {{ trans('common.toggle_all') }}
@include('settings.roles.parts.checkbox', ['permission' => 'page-create-own', 'label' => trans('settings.role_own')]) @@ -169,7 +169,7 @@
{{ trans('entities.images') }}
- {{ trans('common.toggle_all') }} + {{ trans('common.toggle_all') }}
@include('settings.roles.parts.checkbox', ['permission' => 'image-create-all', 'label' => '']) {{ trans('settings.role_controlled_by_asset') }}1
{{ trans('entities.attachments') }}
- {{ trans('common.toggle_all') }} + {{ trans('common.toggle_all') }}
@include('settings.roles.parts.checkbox', ['permission' => 'attachment-create-all', 'label' => '']) {{ trans('settings.role_controlled_by_asset') }}
{{ trans('entities.comments') }}
- {{ trans('common.toggle_all') }} + {{ trans('common.toggle_all') }}
@include('settings.roles.parts.checkbox', ['permission' => 'comment-create-all', 'label' => '']) {{ trans('settings.role_controlled_by_asset') }}
- - + - - + From 5d37a814fdfcde3bb440e126adb61745a5673930 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 20 Oct 2022 12:18:58 +0100 Subject: [PATCH 51/55] New Crowdin updates (#3737) --- resources/lang/ar/editor.php | 1 + resources/lang/ar/entities.php | 8 +- resources/lang/bg/editor.php | 1 + resources/lang/bg/entities.php | 8 +- resources/lang/bs/editor.php | 1 + resources/lang/bs/entities.php | 8 +- resources/lang/ca/editor.php | 1 + resources/lang/ca/entities.php | 8 +- resources/lang/cs/editor.php | 1 + resources/lang/cs/entities.php | 8 +- resources/lang/cy/editor.php | 1 + resources/lang/cy/entities.php | 8 +- resources/lang/da/editor.php | 1 + resources/lang/da/entities.php | 8 +- resources/lang/de/activities.php | 2 +- resources/lang/de/auth.php | 8 +- resources/lang/de/components.php | 2 +- resources/lang/de/editor.php | 1 + resources/lang/de/entities.php | 8 +- resources/lang/de/errors.php | 12 +- resources/lang/de/settings.php | 12 +- resources/lang/de/validation.php | 4 +- resources/lang/de_informal/activities.php | 10 +- resources/lang/de_informal/auth.php | 16 +- resources/lang/de_informal/common.php | 2 +- resources/lang/de_informal/editor.php | 17 +- resources/lang/de_informal/entities.php | 8 +- resources/lang/de_informal/settings.php | 6 +- resources/lang/el/activities.php | 73 +++++ resources/lang/el/auth.php | 115 +++++++ resources/lang/el/common.php | 104 ++++++ resources/lang/el/components.php | 34 ++ resources/lang/el/editor.php | 172 ++++++++++ resources/lang/el/entities.php | 382 ++++++++++++++++++++++ resources/lang/el/errors.php | 109 ++++++ resources/lang/el/pagination.php | 12 + resources/lang/el/passwords.php | 15 + resources/lang/el/settings.php | 315 ++++++++++++++++++ resources/lang/el/validation.php | 117 +++++++ resources/lang/es/editor.php | 1 + resources/lang/es/entities.php | 8 +- resources/lang/es_AR/editor.php | 1 + resources/lang/es_AR/entities.php | 8 +- resources/lang/et/editor.php | 1 + resources/lang/et/entities.php | 8 +- resources/lang/eu/editor.php | 1 + resources/lang/eu/entities.php | 8 +- resources/lang/fa/editor.php | 1 + resources/lang/fa/entities.php | 10 +- resources/lang/fr/editor.php | 1 + resources/lang/fr/entities.php | 8 +- resources/lang/he/editor.php | 1 + resources/lang/he/entities.php | 8 +- resources/lang/hr/editor.php | 1 + resources/lang/hr/entities.php | 8 +- resources/lang/hu/editor.php | 1 + resources/lang/hu/entities.php | 8 +- resources/lang/id/editor.php | 1 + resources/lang/id/entities.php | 8 +- resources/lang/it/editor.php | 1 + resources/lang/it/entities.php | 8 +- resources/lang/ja/editor.php | 1 + resources/lang/ja/entities.php | 8 +- resources/lang/ko/activities.php | 14 +- resources/lang/ko/editor.php | 111 +++---- resources/lang/ko/entities.php | 8 +- resources/lang/lt/editor.php | 1 + resources/lang/lt/entities.php | 8 +- resources/lang/lv/editor.php | 1 + resources/lang/lv/entities.php | 8 +- resources/lang/nb/editor.php | 1 + resources/lang/nb/entities.php | 8 +- resources/lang/nl/editor.php | 1 + resources/lang/nl/entities.php | 8 +- resources/lang/nl/settings.php | 2 +- resources/lang/pl/activities.php | 14 +- resources/lang/pl/editor.php | 1 + resources/lang/pl/entities.php | 42 +-- resources/lang/pl/errors.php | 2 +- resources/lang/pl/settings.php | 12 +- resources/lang/pt/editor.php | 1 + resources/lang/pt/entities.php | 8 +- resources/lang/pt_BR/activities.php | 16 +- resources/lang/pt_BR/auth.php | 6 +- resources/lang/pt_BR/editor.php | 3 +- resources/lang/pt_BR/entities.php | 68 ++-- resources/lang/pt_BR/errors.php | 2 +- resources/lang/pt_BR/settings.php | 12 +- resources/lang/ro/editor.php | 1 + resources/lang/ro/entities.php | 8 +- resources/lang/ro/settings.php | 10 +- resources/lang/ru/editor.php | 1 + resources/lang/ru/entities.php | 8 +- resources/lang/sk/editor.php | 1 + resources/lang/sk/entities.php | 8 +- resources/lang/sl/editor.php | 1 + resources/lang/sl/entities.php | 8 +- resources/lang/sv/activities.php | 62 ++-- resources/lang/sv/auth.php | 76 ++--- resources/lang/sv/common.php | 18 +- resources/lang/sv/editor.php | 273 ++++++++-------- resources/lang/sv/entities.php | 48 +-- resources/lang/sv/errors.php | 10 +- resources/lang/sv/settings.php | 50 +-- resources/lang/sv/validation.php | 6 +- resources/lang/tr/editor.php | 1 + resources/lang/tr/entities.php | 8 +- resources/lang/uk/editor.php | 1 + resources/lang/uk/entities.php | 8 +- resources/lang/uz/editor.php | 1 + resources/lang/uz/entities.php | 8 +- resources/lang/vi/editor.php | 1 + resources/lang/vi/entities.php | 8 +- resources/lang/zh_CN/editor.php | 1 + resources/lang/zh_CN/entities.php | 8 +- resources/lang/zh_TW/activities.php | 20 +- resources/lang/zh_TW/editor.php | 93 +++--- resources/lang/zh_TW/entities.php | 8 +- resources/lang/zh_TW/errors.php | 2 +- resources/lang/zh_TW/settings.php | 12 +- 120 files changed, 2252 insertions(+), 604 deletions(-) create mode 100644 resources/lang/el/activities.php create mode 100644 resources/lang/el/auth.php create mode 100644 resources/lang/el/common.php create mode 100644 resources/lang/el/components.php create mode 100644 resources/lang/el/editor.php create mode 100644 resources/lang/el/entities.php create mode 100644 resources/lang/el/errors.php create mode 100644 resources/lang/el/pagination.php create mode 100644 resources/lang/el/passwords.php create mode 100644 resources/lang/el/settings.php create mode 100644 resources/lang/el/validation.php diff --git a/resources/lang/ar/editor.php b/resources/lang/ar/editor.php index c38b92389..01bad5302 100644 --- a/resources/lang/ar/editor.php +++ b/resources/lang/ar/editor.php @@ -66,6 +66,7 @@ return [ 'insert_link_title' => 'Insert/Edit Link', 'insert_horizontal_line' => 'Insert horizontal line', 'insert_code_block' => 'Insert code block', + 'edit_code_block' => 'Edit code block', 'insert_drawing' => 'Insert/edit drawing', 'drawing_manager' => 'Drawing manager', 'insert_media' => 'Insert/edit media', diff --git a/resources/lang/ar/entities.php b/resources/lang/ar/entities.php index 3285b0ad6..e19f5bb74 100644 --- a/resources/lang/ar/entities.php +++ b/resources/lang/ar/entities.php @@ -42,10 +42,14 @@ return [ // Permissions and restrictions 'permissions' => 'الأذونات', - 'permissions_intro' => 'عند التفعيل، سوف تأخذ هذه الأذونات أولوية على أي صلاحية أخرى للدور.', - 'permissions_enable' => 'تفعيل الأذونات المخصصة', + 'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', 'permissions_save' => 'حفظ الأذونات', 'permissions_owner' => 'Owner', + 'permissions_role_everyone_else' => 'Everyone Else', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Override permissions for role', // Search 'search_results' => 'نتائج البحث', diff --git a/resources/lang/bg/editor.php b/resources/lang/bg/editor.php index c3102868f..c7526f1e9 100644 --- a/resources/lang/bg/editor.php +++ b/resources/lang/bg/editor.php @@ -66,6 +66,7 @@ return [ 'insert_link_title' => 'Вмъкни/редактирай връзка', 'insert_horizontal_line' => 'Вмъкни хоризонтална линия', 'insert_code_block' => 'Въведи код', + 'edit_code_block' => 'Edit code block', 'insert_drawing' => 'Вмъкни/редактирай рисунка', 'drawing_manager' => 'Управление на рисунките', 'insert_media' => 'Вмъкни/редактирай мултимедия', diff --git a/resources/lang/bg/entities.php b/resources/lang/bg/entities.php index cbb78dc9e..4d9c926f6 100644 --- a/resources/lang/bg/entities.php +++ b/resources/lang/bg/entities.php @@ -42,10 +42,14 @@ return [ // Permissions and restrictions 'permissions' => 'Права', - 'permissions_intro' => 'Веднъж добавени, тези права ще вземат приоритет над всички други установени права.', - 'permissions_enable' => 'Разреши уникални права', + 'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', 'permissions_save' => 'Запази права', 'permissions_owner' => 'Собственик', + 'permissions_role_everyone_else' => 'Everyone Else', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Override permissions for role', // Search 'search_results' => 'Резултати от търсенето', diff --git a/resources/lang/bs/editor.php b/resources/lang/bs/editor.php index da7ffcf01..faf351da2 100644 --- a/resources/lang/bs/editor.php +++ b/resources/lang/bs/editor.php @@ -66,6 +66,7 @@ return [ 'insert_link_title' => 'Insert/Edit Link', 'insert_horizontal_line' => 'Insert horizontal line', 'insert_code_block' => 'Insert code block', + 'edit_code_block' => 'Edit code block', 'insert_drawing' => 'Insert/edit drawing', 'drawing_manager' => 'Drawing manager', 'insert_media' => 'Insert/edit media', diff --git a/resources/lang/bs/entities.php b/resources/lang/bs/entities.php index f8805effe..3672b148d 100644 --- a/resources/lang/bs/entities.php +++ b/resources/lang/bs/entities.php @@ -42,10 +42,14 @@ return [ // Permissions and restrictions 'permissions' => 'Dozvole', - 'permissions_intro' => 'Jednom omogućene, ove dozvole imaju prednost nad dozvolama uloge.', - 'permissions_enable' => 'Omogući prilagođena dopuštenja', + 'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', 'permissions_save' => 'Snimi dozvole', 'permissions_owner' => 'Vlasnik', + 'permissions_role_everyone_else' => 'Everyone Else', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Override permissions for role', // Search 'search_results' => 'Rezultati pretrage', diff --git a/resources/lang/ca/editor.php b/resources/lang/ca/editor.php index da7ffcf01..faf351da2 100644 --- a/resources/lang/ca/editor.php +++ b/resources/lang/ca/editor.php @@ -66,6 +66,7 @@ return [ 'insert_link_title' => 'Insert/Edit Link', 'insert_horizontal_line' => 'Insert horizontal line', 'insert_code_block' => 'Insert code block', + 'edit_code_block' => 'Edit code block', 'insert_drawing' => 'Insert/edit drawing', 'drawing_manager' => 'Drawing manager', 'insert_media' => 'Insert/edit media', diff --git a/resources/lang/ca/entities.php b/resources/lang/ca/entities.php index b31cbaa93..0736e527a 100644 --- a/resources/lang/ca/entities.php +++ b/resources/lang/ca/entities.php @@ -42,10 +42,14 @@ return [ // Permissions and restrictions 'permissions' => 'Permisos', - 'permissions_intro' => 'Si els activeu, aquests permisos tindran més prioritat que qualsevol permís de rol.', - 'permissions_enable' => 'Activa els permisos personalitzats', + 'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', 'permissions_save' => 'Desa els permisos', 'permissions_owner' => 'Propietari', + 'permissions_role_everyone_else' => 'Everyone Else', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Override permissions for role', // Search 'search_results' => 'Resultats de la cerca', diff --git a/resources/lang/cs/editor.php b/resources/lang/cs/editor.php index da7ffcf01..faf351da2 100644 --- a/resources/lang/cs/editor.php +++ b/resources/lang/cs/editor.php @@ -66,6 +66,7 @@ return [ 'insert_link_title' => 'Insert/Edit Link', 'insert_horizontal_line' => 'Insert horizontal line', 'insert_code_block' => 'Insert code block', + 'edit_code_block' => 'Edit code block', 'insert_drawing' => 'Insert/edit drawing', 'drawing_manager' => 'Drawing manager', 'insert_media' => 'Insert/edit media', diff --git a/resources/lang/cs/entities.php b/resources/lang/cs/entities.php index 34515fd77..0b552346e 100644 --- a/resources/lang/cs/entities.php +++ b/resources/lang/cs/entities.php @@ -42,10 +42,14 @@ return [ // Permissions and restrictions 'permissions' => 'Oprávnění', - 'permissions_intro' => 'Pokud je povoleno, tato oprávnění budou mít přednost před všemi nastavenými oprávněními role.', - 'permissions_enable' => 'Povolit vlastní oprávnění', + 'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', 'permissions_save' => 'Uložit oprávnění', 'permissions_owner' => 'Vlastník', + 'permissions_role_everyone_else' => 'Everyone Else', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Override permissions for role', // Search 'search_results' => 'Výsledky hledání', diff --git a/resources/lang/cy/editor.php b/resources/lang/cy/editor.php index da7ffcf01..faf351da2 100644 --- a/resources/lang/cy/editor.php +++ b/resources/lang/cy/editor.php @@ -66,6 +66,7 @@ return [ 'insert_link_title' => 'Insert/Edit Link', 'insert_horizontal_line' => 'Insert horizontal line', 'insert_code_block' => 'Insert code block', + 'edit_code_block' => 'Edit code block', 'insert_drawing' => 'Insert/edit drawing', 'drawing_manager' => 'Drawing manager', 'insert_media' => 'Insert/edit media', diff --git a/resources/lang/cy/entities.php b/resources/lang/cy/entities.php index 1720801d2..bf6201900 100644 --- a/resources/lang/cy/entities.php +++ b/resources/lang/cy/entities.php @@ -42,10 +42,14 @@ return [ // Permissions and restrictions 'permissions' => 'Permissions', - 'permissions_intro' => 'Once enabled, These permissions will take priority over any set role permissions.', - 'permissions_enable' => 'Enable Custom Permissions', + 'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', 'permissions_save' => 'Save Permissions', 'permissions_owner' => 'Owner', + 'permissions_role_everyone_else' => 'Everyone Else', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Override permissions for role', // Search 'search_results' => 'Search Results', diff --git a/resources/lang/da/editor.php b/resources/lang/da/editor.php index a0235318b..c925391d5 100644 --- a/resources/lang/da/editor.php +++ b/resources/lang/da/editor.php @@ -66,6 +66,7 @@ return [ 'insert_link_title' => 'Indsæt/Rediger Link', 'insert_horizontal_line' => 'Indsæt vandret linje', 'insert_code_block' => 'Indsæt kodeblok', + 'edit_code_block' => 'Edit code block', 'insert_drawing' => 'Indsæt/rediger tegning', 'drawing_manager' => 'Drawing manager', 'insert_media' => 'Indsæt/rediger medie', diff --git a/resources/lang/da/entities.php b/resources/lang/da/entities.php index 365f3e529..22e850d5c 100644 --- a/resources/lang/da/entities.php +++ b/resources/lang/da/entities.php @@ -42,10 +42,14 @@ return [ // Permissions and restrictions 'permissions' => 'Rettigheder', - 'permissions_intro' => 'Når de er aktiveret, vil disse tilladelser have prioritet over alle indstillede rolletilladelser.', - 'permissions_enable' => 'Aktivér tilpassede tilladelser', + 'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', 'permissions_save' => 'Gem tilladelser', 'permissions_owner' => 'Ejer', + 'permissions_role_everyone_else' => 'Everyone Else', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Override permissions for role', // Search 'search_results' => 'Søgeresultater', diff --git a/resources/lang/de/activities.php b/resources/lang/de/activities.php index e7f18f1e0..0d3f337b7 100644 --- a/resources/lang/de/activities.php +++ b/resources/lang/de/activities.php @@ -48,7 +48,7 @@ return [ 'bookshelf_delete_notification' => 'Regal erfolgreich gelöscht', // Favourites - 'favourite_add_notification' => '":name" wurde zu deinen Favoriten hinzugefügt', + 'favourite_add_notification' => '":name" wurde zu Ihren Favoriten hinzugefügt', 'favourite_remove_notification' => '":name" wurde aus Ihren Favoriten entfernt', // MFA diff --git a/resources/lang/de/auth.php b/resources/lang/de/auth.php index f0433ba5d..f72ef56e3 100644 --- a/resources/lang/de/auth.php +++ b/resources/lang/de/auth.php @@ -35,7 +35,7 @@ return [ '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.', + 'registration_email_domain_invalid' => 'Sie können sich mit dieser E-Mail-Adresse nicht registrieren', 'register_success' => 'Vielen Dank für Ihre Registrierung! Die Daten sind gespeichert und Sie sind angemeldet.', // Login auto-initiation @@ -58,7 +58,7 @@ return [ '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:', '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 Ihrer E-Mail-Adresse nicht versandt werden. Bitte kontaktieren Sie den Systemadministrator!', + '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 wurde bestätigt! Sie sollten nun in der Lage sein, sich mit dieser E-Mail-Adresse anzumelden.', 'email_confirm_resent' => 'Bestätigungs-E-Mail wurde erneut versendet, bitte überprüfen Sie Ihren Posteingang.', @@ -69,12 +69,12 @@ return [ 'email_not_confirmed_resend_button' => 'Bestätigungs-E-Mail erneut senden', // User Invite - 'user_invite_email_subject' => 'Du wurdest eingeladen :appName beizutreten!', + 'user_invite_email_subject' => 'Sie wurden eingeladen :appName beizutreten!', 'user_invite_email_greeting' => 'Ein Konto wurde für Sie auf :appName erstellt.', 'user_invite_email_text' => 'Klicken Sie auf die Schaltfläche unten, um ein Passwort festzulegen und Zugriff zu erhalten:', 'user_invite_email_action' => 'Account-Passwort festlegen', 'user_invite_page_welcome' => 'Willkommen bei :appName!', - 'user_invite_page_text' => 'Um die Anmeldung abzuschließen und Zugriff auf :appName zu bekommen muss noch ein Passwort festgelegt werden. Dieses wird in Zukunft zum Einloggen benötigt.', + 'user_invite_page_text' => 'Um die Anmeldung abzuschließen und Zugriff auf :appName zu bekommen muss noch ein Passwort festgelegt werden. Dieses wird in Zukunft für die Anmeldung benötigt.', 'user_invite_page_confirm_button' => 'Passwort bestätigen', 'user_invite_success_login' => 'Passwort gesetzt, Sie sollten nun in der Lage sein, sich mit Ihrem Passwort an :appName anzumelden!', diff --git a/resources/lang/de/components.php b/resources/lang/de/components.php index bda1ce376..7036888f4 100644 --- a/resources/lang/de/components.php +++ b/resources/lang/de/components.php @@ -17,7 +17,7 @@ return [ 'image_delete_used' => 'Dieses Bild wird auf den folgenden Seiten benutzt. ', 'image_delete_confirm_text' => 'Möchten Sie dieses Bild wirklich löschen?', 'image_select_image' => 'Bild auswählen', - 'image_dropzone' => 'Ziehen Sie Bilder hierher oder klicken Sie, um ein Bild auszuwählen', + 'image_dropzone' => 'Ziehen Sie Bilder hierher oder klicken Sie hier, um ein Bild auszuwählen', 'images_deleted' => 'Bilder gelöscht', 'image_preview' => 'Bildvorschau', 'image_upload_success' => 'Bild erfolgreich hochgeladen', diff --git a/resources/lang/de/editor.php b/resources/lang/de/editor.php index 2fff8c7ea..2c14b6410 100644 --- a/resources/lang/de/editor.php +++ b/resources/lang/de/editor.php @@ -66,6 +66,7 @@ return [ 'insert_link_title' => 'Link einfügen/ändern', 'insert_horizontal_line' => 'Horizontale Linie einfügen', 'insert_code_block' => 'Code-Block einfügen', + 'edit_code_block' => 'Edit code block', 'insert_drawing' => 'Zeichnung einfügen/ändern', 'drawing_manager' => 'Zeichnungsmanager', 'insert_media' => 'Medien einfügen/ändern', diff --git a/resources/lang/de/entities.php b/resources/lang/de/entities.php index f83225ddc..8255cf5a5 100644 --- a/resources/lang/de/entities.php +++ b/resources/lang/de/entities.php @@ -42,10 +42,14 @@ return [ // 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_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', 'permissions_save' => 'Berechtigungen speichern', 'permissions_owner' => 'Besitzer', + 'permissions_role_everyone_else' => 'Everyone Else', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Override permissions for role', // Search 'search_results' => 'Suchergebnisse', diff --git a/resources/lang/de/errors.php b/resources/lang/de/errors.php index cedd85ad7..34b0e715f 100644 --- a/resources/lang/de/errors.php +++ b/resources/lang/de/errors.php @@ -6,7 +6,7 @@ return [ // Permissions 'permission' => 'Sie haben keine Zugriffsberechtigung auf die angeforderte Seite.', - 'permissionJson' => 'Sie haben keine Berechtigung, die angeforderte Aktion auszuführen.', + '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.', @@ -19,9 +19,9 @@ return [ 'ldap_extension_not_installed' => 'LDAP-PHP-Erweiterung ist nicht installiert.', 'ldap_cannot_connect' => 'Die Verbindung zum LDAP-Server ist fehlgeschlagen. Beim initialen Verbindungsaufbau trat ein Fehler auf.', 'saml_already_logged_in' => 'Sie sind bereits angemeldet', - 'saml_user_not_registered' => 'Kein Benutzer mit ID :name registriert und die automatische Registrierung ist deaktiviert', + 'saml_user_not_registered' => 'Es ist kein Benutzer mit ID :name registriert und die automatische Registrierung ist deaktiviert', 'saml_no_email_address' => 'Es konnte keine E-Mail-Adresse für diesen Benutzer in den vom externen Authentifizierungssystem zur Verfügung gestellten Daten gefunden werden', - 'saml_invalid_response_id' => 'Die Anfrage vom externen Authentifizierungssystem wird von einem von dieser Anwendung gestarteten Prozess nicht erkannt. Das Zurückgehen nach einem Login könnte dieses Problem verursachen.', + 'saml_invalid_response_id' => 'Die Anfrage vom externen Authentifizierungssystem wird von einem von dieser Anwendung gestarteten Prozess nicht erkannt. Das Zurückgehen nach einer Anmeldung könnte dieses Problem verursachen.', 'saml_fail_authed' => 'Anmeldung mit :system fehlgeschlagen, System konnte keine erfolgreiche Autorisierung bereitstellen', 'oidc_already_logged_in' => 'Bereits angemeldet', 'oidc_user_not_registered' => 'Der Benutzer :name ist nicht registriert und die automatische Registrierung ist deaktiviert', @@ -33,11 +33,11 @@ return [ 'social_account_email_in_use' => 'Die E-Mail-Adresse ":email" ist bereits registriert. Wenn Sie bereits registriert sind, können Sie Ihr :socialAccount-Konto in Ihren 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. Sie können es in Ihren Profil-Einstellung.', + 'social_account_not_used' => 'Dieses :socialAccount-Konto ist bisher keinem Benutzer zugeordnet. Sie können es in Ihren Profil-Einstellungen zuordnen. ', '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' => 'Dieser Einladungslink ist abgelaufen. Sie können stattdessen versuchen, Ihr Passwort zurückzusetzen.', + 'invite_token_expired' => 'Dieser Einladungslink ist abgelaufen. Sie können stattdessen versuchen Ihr Passwort zurückzusetzen.', // 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.', @@ -78,7 +78,7 @@ return [ // 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.', + 'cannot_add_comment_to_draft' => 'Sie können 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.', diff --git a/resources/lang/de/settings.php b/resources/lang/de/settings.php index 195593802..5daa11232 100644 --- a/resources/lang/de/settings.php +++ b/resources/lang/de/settings.php @@ -25,8 +25,8 @@ return [ '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' => 'Aktiviere Bild-Upload höherer Sicherheit', - '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_secure_images_toggle' => 'Aktiviere höhere Sicherheit für Bild-Uploads', + 'app_secure_images_desc' => 'Aus Leistungsgründen sind alle Bilder öffentlich sichtbar. Diese Option fügt zufällige, schwer zu erratende, Zeichenketten zu Bild-URLs hinzu. Stellen Sie sicher, dass Verzeichnisindizes deaktiviert sind, um einen einfachen Zugriff zu verhindern.', 'app_default_editor' => 'Standard-Seiten-Editor', 'app_default_editor_desc' => 'Wählen Sie aus, welcher Editor standardmäßig beim Bearbeiten neuer Seiten verwendet wird. Dies kann auf einer Seitenebene überschrieben werden, wenn es die Berechtigungen erlauben.', 'app_custom_html' => 'Benutzerdefinierter HTML Inhalt', @@ -37,12 +37,12 @@ return [ Größere Bilder werden verkleinert.', 'app_primary_color' => 'Primäre Anwendungsfarbe', 'app_primary_color_desc' => 'Dies sollte ein HEX Wert sein. -Wenn Sie nicht eingeben, wird die Anwendung auf die Standardfarbe zurückgesetzt.', +Wenn Sie nichts 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_footer_links' => 'Fußzeilen-Links', - 'app_footer_links_desc' => 'Fügen Sie Links hinzu, die innerhalb der Seitenfußzeile angezeigt werden. Diese werden am unteren Ende der meisten Seiten angezeigt, einschließlich derjenigen, die keinen Login benötigen. Sie können die Bezeichnung "trans::" verwenden, um systemdefinierte Übersetzungen zu verwenden. Beispiel: Mit "trans::common.privacy_policy" wird der übersetzte Text "Privacy Policy" bereitgestellt und "trans::common.terms_of_service" liefert den übersetzten Text "Terms of Service".', + 'app_footer_links_desc' => 'Fügen Sie Links hinzu, die innerhalb der Seitenfußzeile angezeigt werden. Diese werden am unteren Ende der meisten Seiten angezeigt, einschließlich derjenigen, die keine Anmeldung benötigen. Sie können die Bezeichnung "trans::" verwenden, um systemdefinierte Übersetzungen zu verwenden. Beispiel: Mit "trans::common.privacy_policy" wird der übersetzte Text "Privacy Policy" bereitgestellt und "trans::common.terms_of_service" liefert den übersetzten Text "Terms of Service".', 'app_footer_links_label' => 'Link-Label', 'app_footer_links_url' => 'Link-URL', 'app_footer_links_add' => 'Fußzeilen-Link hinzufügen', @@ -70,8 +70,8 @@ Wenn Sie nicht eingeben, wird die Anwendung auf die Standardfarbe zurückgesetzt '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_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 diese die 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 diff --git a/resources/lang/de/validation.php b/resources/lang/de/validation.php index df3995f30..1ca1f458e 100644 --- a/resources/lang/de/validation.php +++ b/resources/lang/de/validation.php @@ -44,7 +44,7 @@ return [ 'numeric' => ':attribute muss größer-gleich :value sein.', 'file' => ':attribute muss mindestens :value Kilobytes groß sein.', 'string' => ':attribute muss mindestens :value Zeichen enthalten.', - 'array' => ':attribute muss :value Elemente oder mehr haben.', + 'array' => ':attribute muss :value oder mehr Elemente haben.', ], 'exists' => ':attribute ist ungültig.', 'image' => ':attribute muss ein Bild sein.', @@ -56,7 +56,7 @@ return [ 'ipv6' => ':attribute muss eine gültige IPv6-Adresse sein.', 'json' => 'Das Attribut muss eine gültige JSON-Zeichenfolge sein.', 'lt' => [ - 'numeric' => ':attribute muss kleiner sein :value sein.', + 'numeric' => ':attribute muss kleiner als :value sein.', 'file' => ':attribute muss kleiner als :value Kilobytes sein.', 'string' => ':attribute muss weniger als :value Zeichen haben.', 'array' => ':attribute muss weniger als :value Elemente haben.', diff --git a/resources/lang/de_informal/activities.php b/resources/lang/de_informal/activities.php index abc2235da..8e3ce7515 100644 --- a/resources/lang/de_informal/activities.php +++ b/resources/lang/de_informal/activities.php @@ -28,7 +28,7 @@ return [ // Books 'book_create' => 'erstellt Buch', 'book_create_notification' => 'Buch erfolgreich erstellt', - 'book_create_from_chapter' => 'umgewandeltes Kapitel zum Buch', + 'book_create_from_chapter' => 'Kapitel zu Buch umgewandelt', 'book_create_from_chapter_notification' => 'Kapitel erfolgreich in ein Buch umgewandelt', 'book_update' => 'aktualisiert Buch', 'book_update_notification' => 'Buch erfolgreich aktualisiert', @@ -38,13 +38,13 @@ return [ 'book_sort_notification' => 'Buch erfolgreich umsortiert', // Bookshelves - 'bookshelf_create' => 'created shelf', + 'bookshelf_create' => 'Regal erstellt', 'bookshelf_create_notification' => 'Regal erfolgreich erstellt', - 'bookshelf_create_from_book' => 'converted book to shelf', + 'bookshelf_create_from_book' => 'Buch zu Regal umgewandelt', 'bookshelf_create_from_book_notification' => 'Buch erfolgreich zu einem Regal umgewandelt', - 'bookshelf_update' => 'updated shelf', + 'bookshelf_update' => 'Regal aktualisiert', 'bookshelf_update_notification' => 'Regal erfolgreich aktualisiert', - 'bookshelf_delete' => 'deleted shelf', + 'bookshelf_delete' => 'Regal gelöscht', 'bookshelf_delete_notification' => 'Regal erfolgreich gelöscht', // Favourites diff --git a/resources/lang/de_informal/auth.php b/resources/lang/de_informal/auth.php index 1954f46ff..9cff7961d 100644 --- a/resources/lang/de_informal/auth.php +++ b/resources/lang/de_informal/auth.php @@ -99,17 +99,17 @@ return [ 'mfa_gen_totp_desc' => 'Um Mehrfach-Faktor-Authentifizierung nutzen zu können, benötigen Sie eine mobile Anwendung, die TOTP unterstützt, wie Google Authenticator, Authy oder Microsoft Authenticator.', 'mfa_gen_totp_scan' => 'Scannen Sie den QR-Code unten mit ihrer bevorzugten Authentifizierungs-App, um loszulegen.', 'mfa_gen_totp_verify_setup' => 'Setup überprüfen', - 'mfa_gen_totp_verify_setup_desc' => 'Überprüfen Sie, dass alles funktioniert, indem Sie einen Code in Ihrer Authentifizierungs-App in das Eingabefeld unten eingeben:', - 'mfa_gen_totp_provide_code_here' => 'Geben Sie hier Ihre App generierten Code ein', + 'mfa_gen_totp_verify_setup_desc' => 'Überprüfe, dass alles funktioniert, indem du einen Code aus deiner Authentifizierungs-App in das Eingabefeld unten eingibst:', + 'mfa_gen_totp_provide_code_here' => 'Gib hier den von der App generierten Code ein', 'mfa_verify_access' => 'Zugriff überprüfen', - 'mfa_verify_access_desc' => 'Ihr Benutzerkonto erfordert, dass Sie Ihre Identität über eine zusätzliche Verifikationsebene bestätigen, bevor Sie den Zugriff gewähren. Überprüfen Sie mit einer Ihrer konfigurierten Methoden, um fortzufahren.', + 'mfa_verify_access_desc' => 'Dein Benutzerkonto erfordert, dass du deine Identität über eine zusätzliche Verifikationsebene bestätigst, bevor du Zugriff erhältst. Verifiziere diese mit einer deiner konfigurierten Methoden, um fortzufahren.', 'mfa_verify_no_methods' => 'Keine Methoden konfiguriert', - 'mfa_verify_no_methods_desc' => 'Es konnten keine Mehrfach-Faktor-Authentifizierungsmethoden für Ihr Konto gefunden werden. Sie müssen mindestens eine Methode einrichten, bevor Sie Zugriff erhalten.', + 'mfa_verify_no_methods_desc' => 'Es konnten keine Multi-Faktor-Authentifizierungsmethoden für dein Konto gefunden werden. Du musst mindestens eine Methode einrichten, bevor du Zugriff erhältst.', 'mfa_verify_use_totp' => 'Mit einer mobilen App verifizieren', - 'mfa_verify_use_backup_codes' => 'Mit einem Backup-Code überprüfen', + 'mfa_verify_use_backup_codes' => 'Mit einem Backup-Code verifizieren', 'mfa_verify_backup_code' => 'Backup-Code', - 'mfa_verify_backup_code_desc' => 'Geben Sie einen Ihrer verbleibenden Backup-Codes unten ein:', + 'mfa_verify_backup_code_desc' => 'Gib einen deiner verbleibenden Backup-Codes unten ein:', 'mfa_verify_backup_code_enter_here' => 'Backup-Code hier eingeben', - 'mfa_verify_totp_desc' => 'Geben Sie den Code ein, der mit Ihrer mobilen App generiert wurde:', - 'mfa_setup_login_notification' => 'Multi-Faktor-Methode konfiguriert. Bitte melden Sie sich jetzt erneut mit der konfigurierten Methode an.', + 'mfa_verify_totp_desc' => 'Gib den Code ein, der mit deiner mobilen App generiert wurde:', + 'mfa_setup_login_notification' => 'Multi-Faktor-Methode konfiguriert. Bitte melde dich jetzt erneut mit der konfigurierten Methode an.', ]; diff --git a/resources/lang/de_informal/common.php b/resources/lang/de_informal/common.php index bb4c28be2..5055c399e 100644 --- a/resources/lang/de_informal/common.php +++ b/resources/lang/de_informal/common.php @@ -77,7 +77,7 @@ return [ 'status_active' => 'Aktiv', 'status_inactive' => 'Inaktiv', 'never' => 'Niemals', - 'none' => 'Nichts', + 'none' => 'Keine', // Header 'header_menu_expand' => 'Header-Menü erweitern', diff --git a/resources/lang/de_informal/editor.php b/resources/lang/de_informal/editor.php index 8551fdfb4..f136579d8 100644 --- a/resources/lang/de_informal/editor.php +++ b/resources/lang/de_informal/editor.php @@ -9,7 +9,7 @@ return [ // General editor terms 'general' => 'Allgemein', 'advanced' => 'Erweitert', - 'none' => 'Keine Auswahl', + 'none' => 'Keine', 'cancel' => 'Abbrechen', 'save' => 'Speichern', 'close' => 'Schließen', @@ -18,9 +18,9 @@ return [ 'left' => 'Links', 'center' => 'Zentriert', 'right' => 'Rechts', - 'top' => 'Nach oben', + 'top' => 'Oben', 'middle' => 'Mittig', - 'bottom' => 'Nach unten', + 'bottom' => 'Unten', 'width' => 'Breite', 'height' => 'Höhe', 'More' => 'Mehr', @@ -66,11 +66,12 @@ return [ 'insert_link_title' => 'Link einfügen/bearbeiten', 'insert_horizontal_line' => 'Horizontale Linie einfügen', 'insert_code_block' => 'Codeblock einfügen', + 'edit_code_block' => 'Edit code block', 'insert_drawing' => 'Zeichnung einfügen/bearbeiten', 'drawing_manager' => 'Zeichnungsmanager', 'insert_media' => 'Medien einfügen/bearbeiten', 'insert_media_title' => 'Medien einfügen/bearbeiten', - 'clear_formatting' => 'Formatierung zurücksetzen', + 'clear_formatting' => 'Formatierung löschen', 'source_code' => 'Quellcode', 'source_code_title' => 'Quellcode', 'fullscreen' => 'Vollbild', @@ -130,8 +131,8 @@ return [ 'cell_border_double' => 'Doppelt', 'cell_border_groove' => 'Rille', 'cell_border_ridge' => 'Erhaben', - 'cell_border_inset' => 'vertiefte Fläche', - 'cell_border_outset' => 'erhabene Fläche', + 'cell_border_inset' => 'Vertiefte Fläche', + 'cell_border_outset' => 'Erhabene Fläche', 'cell_border_none' => 'Keiner', 'cell_border_hidden' => 'Versteckt', @@ -139,7 +140,7 @@ return [ 'source' => 'Quelle', 'alt_desc' => 'Alternative Beschreibung', 'embed' => 'Einbetten', - 'paste_embed' => 'Fügen Sie Ihren Einbettungscode unten ein:', + 'paste_embed' => 'Füge deinen Einbettungscode unten ein:', 'url' => 'URL', 'text_to_display' => 'Anzuzeigender Text', 'title' => 'Titel', @@ -160,7 +161,7 @@ return [ 'editor_tiny_license' => 'Dieser Editor wurde mit :tinyLink erstellt, das unter der MIT-Lizenz zur Verfügung gestellt wird.', 'editor_tiny_license_link' => 'Die Copyright- und Lizenzdetails von TinyMCE findest du hier.', 'save_continue' => 'Seite speichern & fortfahren', - 'callouts_cycle' => '(Drücken Sie weiter, um durch Typen umzuschalten)', + 'callouts_cycle' => '(Drücke weiter, um durch die Typen zu schalten)', 'link_selector' => 'Inhalt verlinken', 'shortcuts' => 'Kürzel', 'shortcut' => 'Kürzel', diff --git a/resources/lang/de_informal/entities.php b/resources/lang/de_informal/entities.php index 2f1c0136c..3d8842874 100644 --- a/resources/lang/de_informal/entities.php +++ b/resources/lang/de_informal/entities.php @@ -42,10 +42,14 @@ return [ // Permissions and restrictions 'permissions' => 'Berechtigungen', - 'permissions_intro' => 'Wenn benutzerdefinierte Berechtigungen aktiviert werden, überschreiben diese Einstellungen durch Rollen zugewiesene Berechtigungen.', - 'permissions_enable' => 'Benutzerdefinierte Berechtigungen aktivieren', + 'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', 'permissions_save' => 'Berechtigungen speichern', 'permissions_owner' => 'Besitzer', + 'permissions_role_everyone_else' => 'Everyone Else', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Override permissions for role', // Search 'search_results' => 'Suchergebnisse', diff --git a/resources/lang/de_informal/settings.php b/resources/lang/de_informal/settings.php index 9c4af384e..a9aefc8e2 100644 --- a/resources/lang/de_informal/settings.php +++ b/resources/lang/de_informal/settings.php @@ -92,9 +92,9 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung 'maint_send_test_email_mail_text' => 'Glückwunsch! Da du diese E-Mail Benachrichtigung erhalten hast, scheinen deine E-Mail-Einstellungen korrekt konfiguriert zu sein.', 'maint_recycle_bin_desc' => 'Gelöschte Regale, Bücher, Kapitel & Seiten werden in den Papierkorb verschoben, so dass sie wiederhergestellt oder dauerhaft gelöscht werden können. Ältere Einträge im Papierkorb können, in Abhängigkeit von der Systemkonfiguration, nach einer Weile automatisch entfernt werden.', 'maint_recycle_bin_open' => 'Papierkorb öffnen', - 'maint_regen_references' => 'Regenerate References', - 'maint_regen_references_desc' => 'This action will rebuild the cross-item reference index within the database. This is usually handled automatically but this action can be useful to index old content or content added via unofficial methods.', - 'maint_regen_references_success' => 'Reference index has been regenerated!', + 'maint_regen_references' => 'Verweise neu generieren', + 'maint_regen_references_desc' => 'Diese Aktion wird den Verweisindex innerhalb der Datenbank neu erstellen. Dies wird normalerweise automatisch ausgeführt, aber diese Aktion kann nützlich sein, um alte Inhalte oder Inhalte zu indizieren, die mittels inoffizieller Methoden hinzugefügt wurden.', + 'maint_regen_references_success' => 'Verweisindex wurde neu generiert!', 'maint_timeout_command_note' => 'Hinweis: Die Ausführung dieser Aktion kann einige Zeit in Anspruch nehmen, was in einigen Webumgebungen zu Timeout-Problemen führen kann. Alternativ kann diese Aktion auch mit einem Terminalbefehl ausgeführt werden.', // Recycle Bin diff --git a/resources/lang/el/activities.php b/resources/lang/el/activities.php new file mode 100644 index 000000000..d63de883e --- /dev/null +++ b/resources/lang/el/activities.php @@ -0,0 +1,73 @@ + 'σελίδα που δημιουργήθηκε', + 'page_create_notification' => 'Η σελίδα δημιουργήθηκε με επιτυχία', + 'page_update' => 'ενημερωμένη σελίδα', + 'page_update_notification' => 'Η σελίδα ενημερώθηκε με επιτυχία', + 'page_delete' => 'διαγραμμένη σελίδα', + 'page_delete_notification' => 'Η σελίδα διαγράφηκε επιτυχώς', + 'page_restore' => 'αποκατεστημένη σελίδα', + 'page_restore_notification' => 'Η σελίδα αποκαταστάθηκε με επιτυχία', + 'page_move' => 'Η σελίδα μετακινήθηκε', + + // Chapters + 'chapter_create' => 'δημιουργήθηκε κεφάλαιο', + 'chapter_create_notification' => 'Το κεφάλαιο δημιουργήθηκε με επιτυχία', + 'chapter_update' => 'ενημερωμένο κεφάλαιο', + 'chapter_update_notification' => 'Το κεφάλαιο ενημερώθηκε με επιτυχία', + 'chapter_delete' => 'διαγραμμένο κεφάλαιο', + 'chapter_delete_notification' => 'Το κεφάλαιο διαγράφηκε επιτυχώς', + 'chapter_move' => 'το κεφάλαιο μετακινήθηκε', + + // Books + 'book_create' => 'το βιβλίο δημιουργήθηκε', + 'book_create_notification' => 'Το βιβλίο δημιουργήθηκε με επιτυχία', + 'book_create_from_chapter' => 'Το κεφάλαιο μετατράπηκε επιτυχώς σε βιβλίο', + 'book_create_from_chapter_notification' => 'Το κεφάλαιο μετατράπηκε επιτυχώς σε βιβλίο', + 'book_update' => 'ενημερωμένο βιβλίο', + 'book_update_notification' => 'Το βιβλίο ενημερώθηκε με επιτυχία', + 'book_delete' => 'διαγραμμένο βιβλίο', + 'book_delete_notification' => 'Το βιβλίο διαγράφηκε επιτυχώς', + 'book_sort' => 'ταξινομημένο βιβλίο', + 'book_sort_notification' => 'Το βιβλίο επαναταξινομήθηκε επιτυχώς', + + // Bookshelves + 'bookshelf_create' => 'δημιουργήθηκε ράφι', + 'bookshelf_create_notification' => 'Το ράφι δημιουργήθηκε με επιτυχία', + 'bookshelf_create_from_book' => 'το βιβλίο μετατράπηκε σε ράφι', + 'bookshelf_create_from_book_notification' => 'Το βιβλίο μετατράπηκε σε ράφι επιτυχώς', + 'bookshelf_update' => 'ενημερωμένο ράφι', + 'bookshelf_update_notification' => 'Το ράφι ενημερώθηκε επιτυχώς', + 'bookshelf_delete' => 'διαγραμμένο ράφι', + 'bookshelf_delete_notification' => 'Το ράφι ενημερώθηκε επιτυχώς', + + // Favourites + 'favourite_add_notification' => '":name" προστέθηκε στα αγαπημένα σας', + 'favourite_remove_notification' => '":name" προστέθηκε στα αγαπημένα σας', + + // MFA + 'mfa_setup_method_notification' => 'Η μέθοδος πολλαπλών παραγόντων διαμορφώθηκε επιτυχώς', + 'mfa_remove_method_notification' => 'Η μέθοδος πολλαπλών παραγόντων καταργήθηκε με επιτυχία', + + // Webhooks + 'webhook_create' => 'Το webhook δημιουργήθηκε', + 'webhook_create_notification' => 'Το Webhook δημιουργήθηκε με επιτυχία', + 'webhook_update' => 'ενημερωμένο webhook', + 'webhook_update_notification' => 'Το Webhook ενημερώθηκε με επιτυχία', + 'webhook_delete' => 'διαγραμμένο webhook', + 'webhook_delete_notification' => 'Το Webhook διαγράφηκε επιτυχώς', + + // Users + 'user_update_notification' => 'Ο Χρήστης ενημερώθηκε με επιτυχία', + 'user_delete_notification' => 'Ο Χρήστης αφαιρέθηκε επιτυχώς', + + // Other + 'commented_on' => 'σχολίασε', + 'permissions_update' => 'ενημερωμένα δικαιώματα', +]; diff --git a/resources/lang/el/auth.php b/resources/lang/el/auth.php new file mode 100644 index 000000000..a4cd8e023 --- /dev/null +++ b/resources/lang/el/auth.php @@ -0,0 +1,115 @@ + 'Αυτά τα διαπιστευτήρια δεν ταιριάζουν με τα αρχεία μας.', + 'throttle' => 'Πάρα πολλές προσπάθειες σύνδεσης. Δοκιμάστε ξανά σε :δευτερόλεπτα.', + + // Login & Register + 'sign_up' => 'Εγγραφείτε', + 'log_in' => 'Σύνδεση', + 'log_in_with' => 'Συνδεθείτε με το :socialDriver', + 'sign_up_with' => 'Εγγραφείτε με το :socialDriver', + 'logout' => 'Αποσύνδεση', + + 'name' => 'Όνομα', + 'username' => 'Όνομα χρήστη', + 'email' => 'Email', + 'password' => 'Ο κωδικός σας', + 'password_confirm' => 'Επιβεβαιώστε τον κωδικό σας', + 'password_hint' => 'Πρέπει να αποτελείται από τουλάχιστον 8 χαρακτήρες', + 'forgot_password' => 'Ξεχάσατε τον κωδικό σας;', + 'remember_me' => 'Θυμήσου με', + 'ldap_email_hint' => 'Εισαγάγετε ένα email για χρήση για αυτόν τον λογαριασμό.', + 'create_account' => 'Δημιουργήστε λογαριασμό', + 'already_have_account' => 'Έχετε ήδη λογαριασμό;', + 'dont_have_account' => 'Δεν έχετε λογαριασμό;', + 'social_login' => 'Είσοδος με Social MEdia', + 'social_registration' => 'Εγγραφήμε Social MEdia ', + 'social_registration_text' => 'Εγγραφείτε και συνδεθείτε χρησιμοποιώντας άλλη υπηρεσία.', + + 'register_thanks' => 'Ευχαριστούμε για την εγγραφή!', + 'register_confirm' => 'Ελέγξτε το email σας και κάντε κλικ στο κουμπί επιβεβαίωσης για πρόσβαση στο :appName.', + 'registrations_disabled' => 'Οι εγγραφές είναι προς το παρόν απενεργοποιημένες', + 'registration_email_domain_invalid' => 'Αυτός ο τομέας ηλεκτρονικού ταχυδρομείου δεν έχει πρόσβαση σε αυτήν την εφαρμογή', + 'register_success' => 'Ευχαριστούμε για την εγγραφή! Είστε πλέον εγγεγραμμένοι και συνδεδεμένοι.', + + // Login auto-initiation + 'auto_init_starting' => 'Προσπάθεια Σύνδεσης', + 'auto_init_starting_desc' => 'We\'re contacting your authentication system to start the login process. If there\'s no progress after 5 seconds you can try clicking the link below.', + 'auto_init_start_link' => 'Proceed with authentication', + + // Password Reset + 'reset_password' => 'Επαναφορά κωδικού πρόσβασης', + 'reset_password_send_instructions' => 'Εισαγάγετε τη διεύθυνση του email σας παρακάτω και θα σας σταλεί ένα μήνυμα με έναν σύνδεσμο επαναφοράς του κωδικού πρόσβασης.', + 'reset_password_send_button' => 'Αποστολή Συνδέσμου Επαναφοράς', + 'reset_password_sent' => 'Ένας σύνδεσμος επαναφοράς κωδικού πρόσβασης θα αποσταλεί στο :email εάν αυτή η διεύθυνση email βρεθεί στο σύστημα.', + 'reset_password_success' => 'Ο κωδικός πρόσβασής σας επαναφέρθηκε με επιτυχία.', + 'email_reset_subject' => 'Επαναφέρετε τον κωδικό πρόσβασης :appName', + 'email_reset_text' => 'Λαμβάνετε αυτό το μήνυμα ηλεκτρονικού ταχυδρομείου επειδή λάβαμε ένα αίτημα επαναφοράς κωδικού πρόσβασης για τον λογαριασμό σας.', + 'email_reset_not_requested' => 'Εάν δεν ζητήσατε επαναφορά του κωδικού πρόσβασης, δεν απαιτείται καμία περαιτέρω ενέργεια.', + + // Email Confirmation + 'email_confirm_subject' => 'Επιβεβαιώστε το email σας στο :appName', + 'email_confirm_greeting' => 'Ευχαριστούμε για τη συμμετοχή σας στο :appName!', + 'email_confirm_text' => 'Επιβεβαιώστε τη διεύθυνση email σας κάνοντας κλικ στο παρακάτω κουμπί:', + 'email_confirm_action' => 'Επιβεβαίωση διεύθυνσης ηλεκτρονικού ταχυδρομείου', + 'email_confirm_send_error' => 'Απαιτείται επιβεβαίωση μέσω email, αλλά το σύστημα δεν μπόρεσε να στείλει το email. Επικοινωνήστε με τον διαχειριστή για να βεβαιωθείτε ότι το email έχει ρυθμιστεί σωστά.', + 'email_confirm_success' => 'Το email σας επιβεβαιώθηκε! Θα πρέπει τώρα να μπορείτε να συνδεθείτε χρησιμοποιώντας αυτήν τη διεύθυνση email.', + 'email_confirm_resent' => 'Το email επιβεβαίωσης στάλθηκε εκ νέου. Ελέγξτε τα εισερχόμενά σας.', + + 'email_not_confirmed' => 'Η διεύθυνση email δεν επιβεβαιώθηκε', + 'email_not_confirmed_text' => 'Η διεύθυνση email σας δεν έχει ακόμη επιβεβαιωθεί.', + 'email_not_confirmed_click_link' => 'Κάντε κλικ στο σύνδεσμο στο email που στάλθηκε λίγο μετά την εγγραφή σας.', + 'email_not_confirmed_resend' => 'Εάν δεν μπορείτε να βρείτε το email, μπορείτε να στείλετε ξανά το email επιβεβαίωσης υποβάλλοντας την παρακάτω φόρμα.', + '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_login' => 'Ορίστηκε κωδικός πρόσβασης, θα πρέπει τώρα να μπορείτε να συνδεθείτε χρησιμοποιώντας τον καθορισμένο κωδικό πρόσβασης για να αποκτήσετε πρόσβαση στο :appName!', + + // Multi-factor Authentication + 'mfa_setup' => 'Ρύθμιση ελέγχου ταυτότητας πολλαπλών παραγόντων', + 'mfa_setup_desc' => 'Ρυθμίστε τον έλεγχο ταυτότητας πολλαπλών παραγόντων ως ένα επιπλέον επίπεδο ασφάλειας για τον λογαριασμό χρήστη σας.', + 'mfa_setup_configured' => 'Έχει ήδη διαμορφωθεί', + 'mfa_setup_reconfigure' => 'Επαναδιαμόρφωση', + 'mfa_setup_remove_confirmation' => 'Είστε βέβαιοι ότι θέλετε να καταργήσετε αυτήν τη μέθοδο ελέγχου ταυτότητας πολλαπλών παραγόντων;', + 'mfa_setup_action' => 'Ρύθμιση', + 'mfa_backup_codes_usage_limit_warning' => 'Έχετε λιγότερους από 5 εφεδρικούς κωδικούς που απομένουν. Δημιουργήστε και αποθηκεύστε ένα νέο σύνολο προτού εξαντληθούν οι κωδικοί για να αποφύγετε τον αποκλεισμό του λογαριασμού σας.', + 'mfa_option_totp_title' => 'Εφαρμογή για κινητό', + 'mfa_option_totp_desc' => 'Για να χρησιμοποιήσετε τον έλεγχο ταυτότητας πολλαπλών παραγόντων, θα χρειαστείτε μια εφαρμογή για κινητά που υποστηρίζει TOTP, όπως το Google Authenticator, το Authy ή το Microsoft Authenticator.', + 'mfa_option_backup_codes_title' => 'Εφεδρικοί κωδικοί', + 'mfa_option_backup_codes_desc' => 'Αποθηκεύστε με ασφάλεια ένα σύνολο εφεδρικών κωδικών μίας χρήσης τους οποίους μπορείτε να εισαγάγετε για να επαληθεύσετε την ταυτότητά σας.', + 'mfa_gen_confirm_and_enable' => 'Επιβεβαίωση και ενεργοποίηση', + 'mfa_gen_backup_codes_title' => 'Ρύθμιση εφεδρικών κωδικών', + 'mfa_gen_backup_codes_desc' => 'Αποθηκεύστε την παρακάτω λίστα κωδικών σε ασφαλές μέρος. Κατά την πρόσβαση στο σύστημα, θα μπορείτε να χρησιμοποιήσετε έναν από τους κωδικούς ως δεύτερο μηχανισμό ελέγχου ταυτότητας.', + 'mfa_gen_backup_codes_download' => 'Λήψη κωδικών', + 'mfa_gen_backup_codes_usage_warning' => 'Κάθε κωδικός μπορεί να χρησιμοποιηθεί μόνο μία φορά', + 'mfa_gen_totp_title' => 'Ρύθμιση εφαρμογής για κινητά', + 'mfa_gen_totp_desc' => 'Για να χρησιμοποιήσετε τον έλεγχο ταυτότητας πολλαπλών παραγόντων, θα χρειαστείτε μια εφαρμογή για κινητά που υποστηρίζει TOTP, όπως το Google Authenticator, το Authy ή το Microsoft Authenticator.', + 'mfa_gen_totp_scan' => 'Σαρώστε τον παρακάτω κωδικό QR χρησιμοποιώντας την προτιμώμενη εφαρμογή ελέγχου ταυτότητας για να ξεκινήσετε.', + 'mfa_gen_totp_verify_setup' => 'Επαληθεύστε τη ρύθμιση', + 'mfa_gen_totp_verify_setup_desc' => 'Επαληθεύστε ότι όλα λειτουργούν εισάγοντας έναν κωδικό, που δημιουργήθηκε στην εφαρμογή ελέγχου ταυτότητας, στο παρακάτω πλαίσιο εισαγωγής:', + 'mfa_gen_totp_provide_code_here' => 'Εισάγετε τον κώδικα που δημιουργήθηκε από την εφαρμογή σας εδώ', + 'mfa_verify_access' => 'Επαλήθευση πρόσβασης', + 'mfa_verify_access_desc' => 'Ο λογαριασμός σας απαιτεί να επιβεβαιώσετε την ταυτότητά σας μέσω ενός πρόσθετου επιπέδου επαλήθευσης προτού σας παραχωρηθεί πρόσβαση. Επαληθεύστε χρησιμοποιώντας μία από τις διαμορφωμένες μεθόδους σας για να συνεχίσετε.', + 'mfa_verify_no_methods' => 'Δεν έχουν διαμορφωθεί μέθοδοι.', + 'mfa_verify_no_methods_desc' => 'Δεν βρέθηκαν μέθοδοι ελέγχου ταυτότητας πολλαπλών παραγόντων για τον λογαριασμό σας. Θα χρειαστεί να ρυθμίσετε τουλάχιστον μία μέθοδο προτού αποκτήσετε πρόσβαση.', + 'mfa_verify_use_totp' => 'Επαληθεύστε χρησιμοποιώντας μια εφαρμογή για κινητά', + 'mfa_verify_use_backup_codes' => 'Επαληθεύστε χρησιμοποιώντας έναν εφεδρικό κωδικό', + 'mfa_verify_backup_code' => 'Εφεδρικός κωδικός', + 'mfa_verify_backup_code_desc' => 'Εισαγάγετε έναν από τους υπόλοιπους εφεδρικούς κωδικούς σας παρακάτω:', + 'mfa_verify_backup_code_enter_here' => 'Εισαγάγετε τον εφεδρικό κωδικό εδώ:', + 'mfa_verify_totp_desc' => 'Εισαγάγετε τον κωδικό, που δημιουργήθηκε χρησιμοποιώντας την εφαρμογή σας για κινητά, παρακάτω:', + 'mfa_setup_login_notification' => 'Η μέθοδος πολλαπλών παραγόντων έχει διαμορφωθεί. Συνδεθείτε ξανά χρησιμοποιώντας τη ρυθμισμένη μέθοδο.', +]; diff --git a/resources/lang/el/common.php b/resources/lang/el/common.php new file mode 100644 index 000000000..9837e53f0 --- /dev/null +++ b/resources/lang/el/common.php @@ -0,0 +1,104 @@ + 'Ακύρωση', + 'confirm' => 'Οκ', + 'back' => 'Πίσω', + 'save' => 'Αποθήκευση', + 'continue' => 'Συνέχεια', + 'select' => 'Επιλογή', + 'toggle_all' => 'Εναλλαγή όλων', + 'more' => 'Περισσότερα..', + + // Form Labels + 'name' => 'Όνομα', + 'description' => 'Περιγραφή', + 'role' => 'Ρόλος', + 'cover_image' => 'Εικόνα εξώφυλλου', + 'cover_image_description' => 'Αυτή η εικόνα πρέπει να είναι περίπου 440x250px.', + + // Actions + 'actions' => 'Ενέργειες', + 'view' => 'Προβολή', + 'view_all' => 'Προβολή όλων', + 'create' => 'Δημιουργία', + 'update' => 'Ενημέρωση', + 'edit' => 'Επεξεργασία', + 'sort' => 'Ταξινόμηση', + 'move' => 'Μετακίνηση', + 'copy' => 'Αντιγραφή', + 'reply' => 'Απάντηση', + 'delete' => 'Διαγραφή', + 'delete_confirm' => 'Επιβεβαίωση Διαγραφής', + 'search' => 'Αναζήτηση', + 'search_clear' => 'Εκκαθάριση Αναζήτησης', + 'reset' => 'Επαναφορά', + 'remove' => 'Αφαίρεση', + 'add' => 'Προσθήκη', + 'configure' => 'Διαμόρφωση', + 'fullscreen' => 'Πλήρης οθόνη', + 'favourite' => 'Αγαπημένα', + 'unfavourite' => 'Αφαίρεση από Αγαπημένα', + 'next' => 'Επόμενη', + 'previous' => 'Προηγούμενη', + 'filter_active' => 'Ενεργό φίλτρο:', + 'filter_clear' => 'Διαγραφή φίλτρου', + 'download' => 'Λήψη', + 'open_in_tab' => 'Άνοιγμα σε Καρτέλα', + + // Sort Options + 'sort_options' => 'Επιλογές ταξινόμησης', + 'sort_direction_toggle' => 'Εναλλαγή κατεύθυνσης ταξινόμησης', + 'sort_ascending' => 'Αύξουσα ταξινόμηση', + 'sort_descending' => 'Ταξινόμηση Φθίνουσα', + 'sort_name' => 'Ονομα', + 'sort_default' => 'Προεπιλογή', + 'sort_created_at' => 'Δημιουργήθηκε', + 'sort_updated_at' => 'Ενημερώθηκε', + + // Misc + 'deleted_user' => 'Διαγραμμένος χρήστης', + 'no_activity' => 'Δεν υπάρχει δραστηριότητα προς εμφάνιση', + 'no_items' => 'Δεν υπάρχουν διαθέσιμα στοιχεία', + 'back_to_top' => 'Επιστροφή στην κορυφή', + 'skip_to_main_content' => 'Μετάβαση στο κύριο περιεχόμενο', + 'toggle_details' => 'Εναλλαγή λεπτομερειών', + 'toggle_thumbnails' => 'Εναλλαγή μικρογραφιών', + 'details' => 'Λεπτομέριες', + 'grid_view' => 'Προβολή σε πλέγμα', + 'list_view' => 'Προβολή σε λίστα', + 'default' => 'Προκαθορισμένο', + 'breadcrumb' => 'Μπάρα πλοήγησης', + 'status' => 'Κατάσταση', + 'status_active' => 'Ενεργός', + 'status_inactive' => 'Αδρανής', + 'never' => 'Ποτέ', + 'none' => 'Κανένας', + + // Header + 'header_menu_expand' => 'Αναπτύξτε το Head Menu', + 'profile_menu' => 'Μενού Προφίλ', + 'view_profile' => 'Προβολή προφίλ', + 'edit_profile' => 'Επεξεργασία προφίλ', + 'dark_mode' => 'Σκουρόχρωμη εμφάνιση', + 'light_mode' => 'Ανοιχτόχρωμη εμφάνιση', + + // Layout tabs + 'tab_info' => 'Πληροφορίες', + 'tab_info_label' => 'Καρτέλα: Εμφάνιση δευτερευουσών πληροφοριών', + 'tab_content' => 'Περιεχόμενο', + 'tab_content_label' => 'Καρτέλα: Εμφάνιση κύριου περιεχομένου', + + // Email Content + 'email_action_help' => 'Εάν αντιμετωπίζετε πρόβλημα κάνοντας κλικ στο κουμπί ":actionText", αντιγράψτε και επικολλήστε την παρακάτω διεύθυνση URL στο πρόγραμμα περιήγησής σας στον ιστό:', + 'email_rights' => 'Ολα τα πνευματικά δικαιώματα διατηρούνται', + + // Footer Link Options + // Not directly used but available for convenience to users. + 'privacy_policy' => 'Πολιτική Απορρήτου', + 'terms_of_service' => 'Όροι χρήσης', +]; diff --git a/resources/lang/el/components.php b/resources/lang/el/components.php new file mode 100644 index 000000000..c59e93add --- /dev/null +++ b/resources/lang/el/components.php @@ -0,0 +1,34 @@ + 'Επιλογή εικόνας', + 'image_all' => 'Όλες', + 'image_all_title' => 'Δείτε όλες τις εικόνες που υπάρχουν στο Server', + 'image_book_title' => 'Προβολή εικόνων που έχουν μεταφορτωθεί σε αυτό το βιβλίο', + 'image_page_title' => 'Προβολή εικόνων που έχουν δημοσιευτεί σε αυτήν τη σελίδα', + 'image_search_hint' => 'Αναζήτηση με όνομα εικόνας', + 'image_uploaded' => 'Μεταφορτώθηκε :uploadedDate', + 'image_load_more' => 'Φόρτωσε περισσότερα', + 'image_image_name' => 'Όνομα εικόνας', + 'image_delete_used' => 'Αυτή η εικόνα χρησιμοποιείται στις παρακάτω σελίδες.', + 'image_delete_confirm_text' => 'Είστε σίγουροι ότι θέλετε να διαγράψετε αυτήν την εικόνα;', + 'image_select_image' => 'Επιλέξτε Εικόνα', + 'image_dropzone' => 'Σύρτε ή κάντε κλικ εδώ για μεταφόρτωση εικόνων', + 'images_deleted' => 'Οι εικόνες διαγράφηκαν', + 'image_preview' => 'Προεπισκόπηση εικόνας', + 'image_upload_success' => 'Η εικόνα μεταφορτώθηκε με επιτυχία', + 'image_update_success' => 'Τα στοιχεία της εικόνας ενημερώθηκαν με επιτυχία', + 'image_delete_success' => 'Η εικόνα διαγράφηκε επιτυχώς', + 'image_upload_remove' => 'Αφαίρεση', + + // Code Editor + 'code_editor' => 'Επεξεργασία κώδικα', + 'code_language' => 'Γλώσσα κώδικα', + 'code_content' => 'Περιεχόμενο κώδικα', + 'code_session_history' => 'Ιστορικό συνεδρίας', + 'code_save' => 'Αποθήκευση Κώδικα', +]; diff --git a/resources/lang/el/editor.php b/resources/lang/el/editor.php new file mode 100644 index 000000000..0f9489e8b --- /dev/null +++ b/resources/lang/el/editor.php @@ -0,0 +1,172 @@ + 'Γενικά', + 'advanced' => 'Για Προχωρημένους', + 'none' => 'None', + 'cancel' => 'Ακύρωση', + 'save' => 'Αποθήκευση', + 'close' => 'Κλείσιμο', + 'undo' => 'Αναίρεση', + 'redo' => 'Επανάληψη', + 'left' => 'Αριστερά', + 'center' => 'Κέντρο', + 'right' => 'Δεξιά', + 'top' => 'Πάνω', + 'middle' => 'Κέντρο', + 'bottom' => 'Κάτω', + 'width' => 'Πλάτος', + 'height' => 'Ύψος', + 'More' => 'Περισσότερα', + 'select' => 'Επιλέξτε...', + + // Toolbar + 'formats' => 'Μορφοποίηση', + 'header_large' => 'Μεγάλη κεφαλίδα', + 'header_medium' => 'Μεσαία κεφαλίδα', + 'header_small' => 'Μικρή κεφαλίδα', + 'header_tiny' => 'Μικροσκοπική κεφαλίδα', + 'paragraph' => 'Παράγραφος', + 'blockquote' => 'Blockquote', + 'inline_code' => 'Ενσωματωμένος κωδικός', + 'callouts' => 'Επεξηγήσεις', + 'callout_information' => 'Πληροφορίες', + 'callout_success' => 'Επιτυχία', + 'callout_warning' => 'Προειδοποίηση', + 'callout_danger' => 'Κίνδυνος', + 'bold' => 'Έντονη γραφή', + 'italic' => 'Πλάγια γραφή', + 'underline' => 'Υπογράμμιση', + 'strikethrough' => 'Διακριτή διαγραφή', + 'superscript' => 'Εκθέτης', + 'subscript' => 'Δείκτης', + 'text_color' => 'Χρώμα κειμένου', + 'custom_color' => 'Προσαρμογή χρώματος', + 'remove_color' => 'Αφαίρεση χρώματος', + 'background_color' => 'Χρώμα φόντου', + 'align_left' => 'Στοίχιση αριστερά', + 'align_center' => 'Στοίχιση κέντρο', + 'align_right' => 'Στοίχιση δεξιά', + 'align_justify' => 'Πλήρης στοίχιση', + 'list_bullet' => 'Λίστα με κουκκίδες', + 'list_numbered' => 'Λίστα με αρίθμηση', + 'list_task' => 'Λίστα εργασιών', + 'indent_increase' => 'Αύξηση εσοχήςt', + 'indent_decrease' => 'Μείωση εσοχής', + 'table' => 'Πίνακας', + 'insert_image' => 'Εισαγωγή εικόνας', + 'insert_image_title' => 'Εισαγωγή/Επεξεργασία εικόνας', + 'insert_link' => 'Εισαγωγή/επεξεργασία συνδέσμου', + 'insert_link_title' => 'Εισαγωγή/Επεξεργασία συνδέσμου', + 'insert_horizontal_line' => 'Εισαγωγή οριζόντιας γραμμής', + 'insert_code_block' => 'Εισαγωγή μπλοκ κώδικα', + 'edit_code_block' => 'Edit code block', + 'insert_drawing' => 'Εισαγωγή/Επεξεργασία σχεδίου', + 'drawing_manager' => 'Διαχειριστής σχεδίασης', + 'insert_media' => 'Εισαγωγή/Επεξεργασία πολυμέσων', + 'insert_media_title' => 'Εισαγωγή/Επεξεργασία πολυμέσων', + 'clear_formatting' => 'Διαγραφή μορφοποίησης', + 'source_code' => 'Πηγαίος κώδικας', + 'source_code_title' => 'Πηγαίος κώδικας', + 'fullscreen' => 'Πλήρης οθόνη', + 'image_options' => 'Επιλογές εικόνας', + + // Tables + 'table_properties' => 'Ιδιότητες πίνακα', + 'table_properties_title' => 'Ιδιότητες πίνακα', + 'delete_table' => 'Διαγραφή πίνακα', + 'insert_row_before' => 'Εισαγωγή γραμμής πάνω', + 'insert_row_after' => 'Εισαγωγή γραμμής κάτω', + 'delete_row' => 'Διαγραφή γραμμής', + 'insert_column_before' => 'Εισαγωγή στήλης αριστερά', + 'insert_column_after' => 'Εισαγωγή στήλης δεξιά', + 'delete_column' => 'Διαγραφή στήλης', + 'table_cell' => 'Κελί', + 'table_row' => 'Γραμμή', + 'table_column' => 'Στήλη', + 'cell_properties' => 'Ιδιότητες κελιού', + 'cell_properties_title' => 'Ιδιότητες κελιού', + 'cell_type' => 'Τύπος κελιού', + 'cell_type_cell' => 'Κελί', + 'cell_scope' => 'Scope', + 'cell_type_header' => 'Κεφαλίδα κελιού', + 'merge_cells' => 'Συγχώνευση κελιών', + 'split_cell' => 'Διαίρεση κελιού', + 'table_row_group' => 'Ομάδα γραμμών', + 'table_column_group' => 'Ομάδα στηλών', + 'horizontal_align' => 'Οριζόντια στοίχιση', + 'vertical_align' => 'Κάθετη στοίχιση', + 'border_width' => 'Πάχος περιγράμματος', + 'border_style' => 'Στυλ περιγράμματος', + 'border_color' => 'Χρώμα περιγράμματος', + 'row_properties' => 'Ιδιότητες γραμμής', + 'row_properties_title' => 'Ιδιότητες γραμμής', + 'cut_row' => 'Αποκοπή γραμμής', + 'copy_row' => 'Αντιγραφή γραμμής', + 'paste_row_before' => 'Επικόλληση γραμμής πάνω', + 'paste_row_after' => 'Επικόλληση γραμμής κάτω', + 'row_type' => 'Τύπος γραμμής', + 'row_type_header' => 'Κεφαλίδα', + 'row_type_body' => 'Σώμα', + 'row_type_footer' => 'Υποσέλιδο', + 'alignment' => 'Ευθυγράμμιση', + 'cut_column' => 'Αποκοπή στήλης', + 'copy_column' => 'Αντιγραφή στήλης', + 'paste_column_before' => 'Επικόλληση στήλης αριστερά', + 'paste_column_after' => 'Επικόλληση στήλης δεξιά', + 'cell_padding' => 'Περιθώριο κελιών', + 'cell_spacing' => 'Απόσταση κελιών', + 'caption' => 'Τίτλος', + 'show_caption' => 'Εμφάνιση Τίτλου', + 'constrain' => 'Περιορισμός αναλογιών', + 'cell_border_solid' => 'Συμπαγής γραμμή', + 'cell_border_dotted' => 'Γραμμή με κουκκίδες', + 'cell_border_dashed' => 'Διακεκομμένη γραμμή', + 'cell_border_double' => 'Διπλή γραμμή', + 'cell_border_groove' => 'Groove', + 'cell_border_ridge' => 'Κορυφογραμμή', + 'cell_border_inset' => 'Inset', + 'cell_border_outset' => 'Outset', + 'cell_border_none' => 'Χωρίς', + 'cell_border_hidden' => '΄Διαφανές', + + // Images, links, details/summary & embed + 'source' => 'Source', + 'alt_desc' => 'Εναλλακτική περιγραφή', + 'embed' => 'Ενσωματωμένο', + 'paste_embed' => 'Επικολλήστε τον κώδικα ενσωμάτωσης παρακάτω:', + 'url' => 'URL', + 'text_to_display' => 'Κείμενο εμφάνισης', + 'title' => 'Τίτλος', + 'open_link' => 'Άνοιγμα συνδέσμου σε...', + 'open_link_current' => 'Τρέχον παράθυρο', + 'open_link_new' => 'Νέο παράθυρο', + 'insert_collapsible' => 'Εισαγωγή πτυσσόμενου μπλοκ', + 'collapsible_unwrap' => 'Μετατροπή πτυσσόμενου μπλοκ σε παράγραφο', + 'edit_label' => 'Επεξεργασία ετικέτας', + 'toggle_open_closed' => 'Εναλλαγή ανοίγματος/κλεισίματος', + 'collapsible_edit' => 'Επεξεργασία πτυσσόμενου μπλοκ', + 'toggle_label' => 'Εναλλαγή ετικέτας', + + // About view + 'about' => 'Σχετικά', + 'about_title' => 'Σχετικά με τον επεξεργαστή WYSIWYG', + 'editor_license' => 'Άδεια εκδότη και πνευματικά δικαιώματα', + 'editor_tiny_license' => 'Αυτός ο επεξεργαστής έχει δημιουργηθεί χρησιμοποιώντας :tinyLink που παρέχεται με την άδεια MIT.', + 'editor_tiny_license_link' => 'Τα πνευματικά δικαιώματα και τα στοιχεία άδειας χρήσης του TinyMCE μπορείτε να τα βρείτε εδώ.', + 'save_continue' => 'Αποθήκευση σελίδας & Συνέχεια', + 'callouts_cycle' => '(Συνεχίστε να πατάτε για εναλλαγή μεταξύ τύπων)', + 'link_selector' => 'Σύνδεσμος προς το περιεχόμενο', + 'shortcuts' => 'Συντομεύσεις', + 'shortcut' => 'Συντόμευση', + 'shortcuts_intro' => 'Οι ακόλουθες συντομεύσεις είναι διαθέσιμες στο πρόγραμμα επεξεργασίας:', + 'windows_linux' => '(Windows/Linux)', + 'mac' => '(Mac)', + 'description' => 'Περιγραφή', +]; diff --git a/resources/lang/el/entities.php b/resources/lang/el/entities.php new file mode 100644 index 000000000..f9f4aef70 --- /dev/null +++ b/resources/lang/el/entities.php @@ -0,0 +1,382 @@ + 'Δημιουργήθηκε Πρόσφατα', + 'recently_created_pages' => 'Πρόσφατα Δημιουργημένες Σελίδες', + 'recently_updated_pages' => 'Πρόσφατες Ενημερώσεις', + 'recently_created_chapters' => 'Πρόσφατα Δημιουργημένα Κεφάλαια', + 'recently_created_books' => 'Πρόσφατα Δημιουργημένα Βιβλία', + 'recently_created_shelves' => 'Πρόσφατα Δημιουργημένα Ράφια', + 'recently_update' => 'Ενημερώθηκε πρόσφατα', + 'recently_viewed' => 'Πρόσφατα προβεβλημένα', + 'recent_activity' => 'Πρόσφατη Δραστηριότητα', + 'create_now' => 'Δημιουργία ενός τώρα', + 'revisions' => 'Αναθεωρήσεις', + 'meta_revision' => 'Αναθεώρηση #:revisionCount', + 'meta_created' => 'Δημιουργήθηκε :timeLength', + 'meta_created_name' => 'Δημιουργήθηκε :timeLength by :user', + 'meta_updated' => 'Ενημερώθηκε :timeLength', + 'meta_updated_name' => 'Ενημερώθηκε :timeLength by :user', + 'meta_owned_name' => 'Ανήκει στον :user', + 'meta_reference_page_count' => 'Αναφορά σε 1 σελίδα"Αναφερόμενη στο :count σελίδες', + 'entity_select' => 'Επιλογή Οντότητας', + 'entity_select_lack_permission' => 'Δεν έχετε τα απαιτούμενα δικαιώματα για να επιλέξετε αυτό το στοιχείο', + 'images' => 'Εικόνες', + 'my_recent_drafts' => 'Τα πρόσφατα προσχέδιά μου', + 'my_recently_viewed' => 'Είδα πρόσφατα', + 'my_most_viewed_favourites' => 'Συχνά Αγαπημένα', + 'my_favourites' => 'Τα αγαπημένα μου', + 'no_pages_viewed' => 'Δεν έχετε δει καμία σελίδα', + 'no_pages_recently_created' => 'Δεν έχουν δημιουργηθεί πρόσφατα σελίδες', + 'no_pages_recently_updated' => 'Δεν υπάρχουν πρόσφατα ενημερώσεις σελίδων', + 'export' => 'Εξαγωγή', + 'export_html' => 'Αρχείο Web', + 'export_pdf' => 'Αρχείο PDF', + 'export_text' => 'Αρχείο Απλού κειμένου', + 'export_md' => 'Αρχείο Markdown', + + // Permissions and restrictions + 'permissions' => 'Δικαιώματα', + 'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', + 'permissions_save' => 'Αποθήκευση Δικαιωμάτων', + 'permissions_owner' => 'Ιδιοκτήτης / Κάτοχος', + 'permissions_role_everyone_else' => 'Everyone Else', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Override permissions for role', + + // Search + 'search_results' => 'Αποτελέσματα αναζήτησης', + 'search_total_results_found' => ':count αποτέλεσμα που βρέθηκε:count συνολικά αποτελέσματα που βρέθηκαν', + 'search_clear' => 'Καθαρισμός αναζήτησης', + 'search_no_pages' => 'Καμία σελίδα δεν ταιριάζει με αυτήν την αναζήτηση', + 'search_for_term' => 'Αναζήτηση για :term', + 'search_more' => 'Περισσότερα αποτελέσματα', + 'search_advanced' => 'Προχωρημένη Αναζήτηση', + 'search_terms' => 'Αναζήτηση Όρων', + '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_owned_by_me' => 'Ανήκει σε μένα', + 'search_date_options' => 'Επιλογές Ημερομηνίας', + 'search_updated_before' => 'Ενημερώθηκε πριν', + 'search_updated_after' => 'Ενημερώθηκε μετά', + 'search_created_before' => 'Δημιουργήθηκε πριν', + 'search_created_after' => 'Δημιουργήθηκε μετά', + 'search_set_date' => 'Ορισμός Ημερομηνίας', + 'search_update' => 'Ενημέρωση Αναζήτησης', + + // Shelves + 'shelf' => 'Ράφι', + 'shelves' => 'Ράφια', + 'x_shelves' => ':count Ράφι|:count Ράφια', + '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' => '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_permissions_cascade_warning' => 'Permissions on bookshelves do not automatically cascade to contained books. This is because a book can exist on multiple shelves. Permissions can however be copied down to child books using the option found below.', + 'shelves_copy_permissions_to_books' => 'Αντιγραφή δικαιωμάτων στα βιβλία', + 'shelves_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 Βιβλία', + 'books_empty' => 'Δεν έχουν δημιουργηθεί βιβλία ακόμα', + 'books_popular' => 'Δημοφιλή Βιβλία', + 'books_recent' => 'Πρόσφατα Βιβλία', + 'books_new' => 'Νέα Βιβλία', + 'books_new_action' => 'Νέο βιβλίο', + 'books_popular_empty' => 'Τα πιο δημοφιλή εμφανίζονται εδώ.', + 'books_new_empty' => 'Θα εμφανιστούν εδώ, αυτά που δημιουργήθηκαν πιο πρόσφατα.', + 'books_create' => 'Δημιουργία νέου βιβλίου', + 'books_delete' => 'Διαγραφή Βιβλίου', + 'books_delete_named' => 'Διαγραφή Βιβλίου :bookname', + 'books_delete_explain' => 'Αυτό θα διαγράψει το βιβλίο με το όνομα \':bookName\'. Όλες οι σελίδες και τα κεφάλαια θα αφαιρεθούν.', + 'books_delete_confirmation' => 'Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό το βιβλίο;', + 'books_edit' => 'Επεξεργασία Βιβλίου', + 'books_edit_named' => 'Επεξεργασία Βιβλίου :bookname', + 'books_form_book_name' => 'Όνομα Βιβλίου', + 'books_save' => 'Αποθήκευση Βιβλίου', + 'books_permissions' => 'Άδειες Βιβλίου', + 'books_permissions_updated' => 'Τα Δικαιώματα Βιβλίου Ενημερώθηκαν', + 'books_empty_contents' => 'Δεν έχουν δημιουργηθεί σελίδες ή κεφάλαια για αυτό το βιβλίο.', + 'books_empty_create_page' => 'Δημιουργία νέας σελίδας', + 'books_empty_sort_current_book' => 'Ταξινόμηση του τρέχοντος βιβλίου', + 'books_empty_add_chapter' => 'Προσθήκη κεφαλαίου', + 'books_permissions_active' => 'Ενεργά Δικαιώματα ´Βιβλίου', + 'books_search_this' => 'Αναζήτηση σε αυτό το βιβλίο', + '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' => 'Αποθήκευση Νέας Ταξινόμησης', + 'books_copy' => 'Αντιγραφή Βιβλίου', + 'books_copy_success' => 'Το βιβλίο αντιγράφηκε επιτυχώς', + + // Chapters + 'chapter' => 'Κεφάλαιο', + 'chapters' => 'Κεφάλαια', + 'x_chapters' => ':count Κεφάλαιο:count Κεφάλαια', + 'chapters_popular' => 'Δημοφιλή Κεφάλαια', + 'chapters_new' => 'Νέο Κεφάλαιο', + 'chapters_create' => 'Δημιουργία Νέου Κεφαλαίου', + 'chapters_delete' => 'Διαγραφή Κεφαλαίου', + 'chapters_delete_named' => 'Διαγραφή Κεφαλαίου :chapterName', + 'chapters_delete_explain' => 'Αυτό θα διαγράψει το κεφάλαιο με το όνομα \':chapterName\'. Όλες οι σελίδες που υπάρχουν μέσα σε αυτό το κεφάλαιο θα διαγραφούν επίσης.', + 'chapters_delete_confirm' => 'Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό το κεφάλαιο;', + 'chapters_edit' => 'Επεξεργασία Κεφαλαίου', + 'chapters_edit_named' => 'Επεξεργασία Κεφαλαίου :chapterName', + 'chapters_save' => 'Αποθήκευση Κεφαλαίου', + 'chapters_move' => 'Μετακίνηση Κεφαλαίου', + 'chapters_move_named' => 'Μετακίνηση Κεφαλαίου :chapterName', + 'chapter_move_success' => 'Το κεφάλαιο μεταφέρθηκε στο:bookName', + 'chapters_copy' => 'Αντιγραφή Κεφαλαίου', + 'chapters_copy_success' => 'Το κεφάλαιο αντιγράφηκε επιτυχώς', + 'chapters_permissions' => 'Δικαιώματα Κεφαλαίου', + 'chapters_empty' => 'Καμία σελίδα δεν βρίσκεται σε αυτό το κεφάλαιο.', + 'chapters_permissions_active' => 'Ενεργά Δικαιώματα Κεφαλαίου', + 'chapters_permissions_success' => 'Τα Δικαιώματα Κεφαλαίου Ενημερώθηκαν', + 'chapters_search_this' => 'Αναζήτηση σε αυτό το κεφάλαιο', + 'chapter_sort_book' => 'Ταξινόμηση Βιβλίου', + + // Pages + 'page' => 'Σελίδα', + 'pages' => 'Σελίδες', + 'x_pages' => ':count Σελίδα:count Σελίδες', + 'pages_popular' => 'Δημοφιλείς Σελίδες', + 'pages_new' => 'Νέα Σελίδα', + 'pages_attachments' => 'Συνημμένα', + 'pages_navigation' => 'Πλοήγηση στη σελίδα', + 'pages_delete' => 'Διαγραφή Σελίδας', + 'pages_delete_named' => 'Διαγραφή Σελίδας :pageName', + 'pages_delete_draft_named' => 'Διαγραφή Προσχέδιας Σελίδας :pageName', + 'pages_delete_draft' => 'Διαγραφή Προσχέδιας Σελίδας', + 'pages_delete_success' => 'Η σελίδα διαγράφηκε', + 'pages_delete_draft_success' => 'Η προσχέδια (πρόχειρη) σελίδα διαγράφηκε', + 'pages_delete_confirm' => 'Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτή τη σελίδα;', + 'pages_delete_draft_confirm' => 'Θέλετε σίγουρα να διαγράψετε την προσχέδια σελίδα;', + 'pages_editing_named' => 'Επεξεργασία Σελίδας :pageName', + 'pages_edit_draft_options' => 'Επιλογές Προσχεδίου', + 'pages_edit_save_draft' => 'Αποθήκευση Προχείρου (Προσχεδίου)', + 'pages_edit_draft' => 'Επεξεργασία Προσχεδίου Σελίδας', + 'pages_editing_draft' => 'Επεξεργασία Προσχεδίου', + 'pages_editing_page' => 'Επεξεργασία Σελίδας', + 'pages_edit_draft_save_at' => 'Το προσχέδιο αποθηκεύτηκε στις ', + 'pages_edit_delete_draft' => 'Διαγραφή Προσχεδίου', + 'pages_edit_discard_draft' => 'Απόρριψη Προσχεδίου', + 'pages_edit_switch_to_markdown' => 'Μετάβαση στον Επεξεργαστή Markdown', + 'pages_edit_switch_to_markdown_clean' => '(Καθαρισμός Περιεχομένου)', + 'pages_edit_switch_to_markdown_stable' => '(Σταθερό Περιεχόμενο)', + 'pages_edit_switch_to_wysiwyg' => 'Εναλλαγή στον επεξεργαστή WYSIWYG', + 'pages_edit_set_changelog' => 'Ορισμός καταγραφής αλλαγών', + 'pages_edit_enter_changelog_desc' => 'Εισάγετε μια σύντομη περιγραφή των αλλαγών που κάνατε', + 'pages_edit_enter_changelog' => 'Εισαγωγή Αρχείου Καταγραφής Αλλαγών', + 'pages_editor_switch_title' => 'Εναλλαγή Επεξεργαστή', + 'pages_editor_switch_are_you_sure' => 'Είστε βέβαιοι ότι θέλετε να αλλάξετε τον επεξεργαστή κειμένου για αυτή τη σελίδα;', + 'pages_editor_switch_consider_following' => 'Λάβετε υπόψη τα ακόλουθα όταν αλλάζετε συντάκτες:', + 'pages_editor_switch_consideration_a' => 'Μόλις αποθηκευτεί, η επιλογή του νέου επεξεργαστή κειμένου θα χρησιμοποιηθεί από τυχόν μελλοντικούς επεξεργαστές, συμπεριλαμβανομένων εκείνων που μπορεί να μην είναι σε θέση να αλλάξουν τον τύπο του επεξεργαστή κειμένου.', + 'pages_editor_switch_consideration_b' => 'Αυτό μπορεί να οδηγήσει σε απώλεια λεπτομερειών και κώδικα σε ορισμένες περιπτώσεις.', + 'pages_editor_switch_consideration_c' => 'Οι αλλαγές ετικετών ή αρχείων καταγραφής, που έγιναν από την τελευταία αποθήκευση, δεν θα συνεχιστούν σε αυτήν την αλλαγή.', + 'pages_save' => 'Αποθήκευση Σελίδας', + 'pages_title' => 'Τίτλος Σελίδας', + 'pages_name' => 'Όνομα Σελίδας', + 'pages_md_editor' => 'Επεξεργαστής', + 'pages_md_preview' => 'Προεπισκόπηση', + 'pages_md_insert_image' => 'Εισαγωγή Εικόνας', + 'pages_md_insert_link' => 'Εισαγωγή/Επεξεργασία συνδέσμου', + 'pages_md_insert_drawing' => 'Εισαγωγή Σχεδίου', + 'pages_not_in_chapter' => 'Η σελίδα δεν είναι σε κεφάλαιο', + 'pages_move' => 'Μετακίνηση Σελίδας', + 'pages_move_success' => 'Η σελίδα μετακινήθηκε στο ":parentName"', + 'pages_copy' => 'Αντιγραφή Σελίδας', + 'pages_copy_desination' => 'Αντιγραφή Προορισμού', + 'pages_copy_success' => 'Η σελίδα αντιγράφηκε επιτυχώς', + 'pages_permissions' => 'Δικαιώματα Σελίδας', + 'pages_permissions_success' => 'Τα δικαιώματα σελίδας ενημερώθηκαν', + 'pages_revision' => 'Αναθεώρηση', + 'pages_revisions' => 'Αναθεωρήσεις Σελίδας', + 'pages_revisions_named' => 'Αναθεωρήσεις σελίδας για :pageName', + 'pages_revision_named' => 'Αναθεώρηση σελίδας για :pageName', + 'pages_revision_restored_from' => 'Επαναφορά από #:id; :summary', + 'pages_revisions_created_by' => 'Δημιουργήθηκε από', + 'pages_revisions_date' => 'Ημερομηνία Αναθεώρησης', + 'pages_revisions_number' => '#', + 'pages_revisions_numbered' => 'Αναθεώρηση #', + 'pages_revisions_numbered_changes' => 'Αναθεώρηση #:id Αλλαγές', + 'pages_revisions_editor' => 'Τύπος Επεξεργαστή', + 'pages_revisions_changelog' => 'Αρχείο καταγραφής αλλαγών', + 'pages_revisions_changes' => 'Αλλαγές', + 'pages_revisions_current' => 'Τρέχουσα Έκδοση', + 'pages_revisions_preview' => 'Προεπισκόπηση', + 'pages_revisions_restore' => 'Επαναφορά', + 'pages_revisions_none' => 'Αυτή η σελίδα δεν έχει αναθεωρήσεις', + 'pages_copy_link' => 'Αντιγραφή Συνδέσμου', + 'pages_edit_content_link' => 'Επεξεργασία Περιεχομένου', + 'pages_permissions_active' => 'Ενεργά Δικαιώματα Σελίδας', + 'pages_initial_revision' => 'Αρχική δημοσίευση', + 'pages_references_update_revision' => 'Αυτόματη ενημέρωση του συστήματος των εσωτερικών συνδέσμων', + 'pages_initial_name' => 'Νέα Σελίδα', + 'pages_editing_draft_notification' => 'Αυτή τη στιγμή επεξεργάζεστε ένα προσχέδιο που αποθηκεύτηκε για τελευταία φορά :timeDiff.', + 'pages_draft_edited_notification' => 'Αυτή η σελίδα έχει ενημερωθεί από εκείνη τη στιγμή. Συνιστάται να απορρίψετε αυτό το προσχέδιο.', + 'pages_draft_page_changed_since_creation' => 'Αυτή η σελίδα έχει ενημερωθεί από τότε που δημιουργήθηκε αυτό το προσχέδιο. Συνιστάται να απορρίψετε αυτό το σχέδιο ή να φροντίσετε να μην αντικαταστήσετε τυχόν αλλαγές σελίδας.', + 'pages_draft_edit_active' => [ + 'start_a' => ':count χρήστες έχουν αρχίσει να επεξεργάζονται αυτή τη σελίδα', + 'start_b' => ':userName έχει ξεκινήσει την επεξεργασία αυτής της σελίδας', + 'time_a' => 'από τότε που η σελίδα ενημερώθηκε τελευταία φορά', + 'time_b' => 'τα τελευταία :mint λεπτά', + 'message' => ':start :time. Προσέξτε να μην αντικαταστήσετε ο ένας τις ενημερώσεις του άλλου!', + ], + 'pages_draft_discarded' => 'Το προσχέδιο απορρίφθηκε, ο επεξεργαστής ενημερώθηκε με το τρέχον περιεχόμενο της σελίδας', + 'pages_specific' => 'Συγκεκριμένη Σελίδα', + 'pages_is_template' => 'Πρότυπο σελίδας', + + // Editor Sidebar + 'page_tags' => 'Ετικέτες Σελίδας', + 'chapter_tags' => 'Ετικέτες Κεφαλαίου', + 'book_tags' => 'Ετικέτες Βιβλίου', + 'shelf_tags' => 'Ετικέτες Ραφιών', + 'tag' => 'Ετικέτα', + 'tags' => 'Ετικέτες', + 'tag_name' => 'Όνομα Ετικέτας', + 'tag_value' => 'Τιμή Ετικέτας (Προαιρετικό)', + 'tags_explain' => "Προσθέστε μερικές ετικέτες για να κατηγοριοποιήσετε καλύτερα το περιεχόμενό σας. \n Μπορείτε να αντιστοιχίσετε μια τιμή σε μια ετικέτα για πιο αναλυτική οργάνωση.", + 'tags_add' => 'Προσθήκη άλλης ετικέτας', + 'tags_remove' => 'Αφαίρεση ετικέτας', + 'tags_usages' => 'Συνολικές χρήσεις ετικετών', + 'tags_assigned_pages' => 'Ανατέθηκε σε σελίδες', + 'tags_assigned_chapters' => 'Ανατέθηκε στα κεφάλαια', + 'tags_assigned_books' => 'Ανατέθηκε σε Βιβλία', + 'tags_assigned_shelves' => 'Ανατέθηκε σε Ράφια', + 'tags_x_unique_values' => ':count μοναδικές τιμές', + 'tags_all_values' => 'Όλες οι τιμές', + 'tags_view_tags' => 'Προβολή Ετικετών', + 'tags_view_existing_tags' => 'Δείτε τις υπάρχουσες ετικέτες', + 'tags_list_empty_hint' => 'Οι ετικέτες μπορούν να εκχωρηθούν μέσω της πλαϊνής μπάρας συντάκτη σελίδας ή κατά την επεξεργασία των λεπτομερειών ενός βιβλίου, κεφαλαίου ή ράφι.', + 'attachments' => 'Συνημμένα', + 'attachments_explain' => 'Ανεβάστε μερικά αρχεία ή επισυνάψτε μερικούς συνδέσμους για να εμφανίσετε στη σελίδα σας. Αυτά είναι ορατά στην πλαϊνή μπάρα σελίδας.', + 'attachments_explain_instant_save' => 'Οι αλλαγές εδώ αποθηκεύονται αμέσως.', + 'attachments_items' => 'Συνημμένα Στοιχεία', + 'attachments_upload' => 'Μεταφόρτωση Αρχείου', + 'attachments_link' => 'Επισύναψη Δεσμού', + 'attachments_set_link' => 'Ορισμός Συνδέσμου', + 'attachments_delete' => 'Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό το συνημμένο;', + 'attachments_dropzone' => 'Αποθέστε αρχεία ή κάντε κλικ εδώ για να επισυνάψετε ένα αρχείο', + 'attachments_no_files' => 'Δεν έχουν μεταφορτωθεί αρχεία', + 'attachments_explain_link' => 'Μπορείτε να επισυνάψετε έναν σύνδεσμο αν προτιμάτε να μην ανεβάσετε ένα αρχείο. Αυτό μπορεί να είναι ένας σύνδεσμος σε άλλη σελίδα ή ένας σύνδεσμος σε ένα αρχείο στο σύννεφο.', + 'attachments_link_name' => 'Όνομα Συνδέσμου', + 'attachment_link' => 'Σύνδεσμος συνημμένου', + 'attachments_link_url' => 'Σύνδεση σε αρχείο', + 'attachments_link_url_hint' => 'Url του ιστότοπου ή του αρχείου', + 'attach' => 'Επισύναψη', + 'attachments_insert_link' => 'Προσθήκη συνημμένου συνδέσμου στη σελίδα', + 'attachments_edit_file' => 'Επεξεργασία Αρχείου', + 'attachments_edit_file_name' => 'Όνομα Αρχείου', + 'attachments_edit_drop_upload' => 'Ρίξτε αρχεία ή κάντε κλικ εδώ για να ανεβάσετε και να αντικαταστήσετε', + 'attachments_order_updated' => 'Η παραγγελία συνημμένων ενημερώθηκε', + 'attachments_updated_success' => 'Οι λεπτομέρειες συνημμένου ενημερώθηκαν', + 'attachments_deleted' => 'Το συνημμένο διαγράφηκε', + '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_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 + 'comment' => 'Σχόλιο', + 'comments' => 'Σχόλια', + 'comment_add' => 'Προσθήκη Σχολίου', + 'comment_placeholder' => 'Αφήστε ένα σχόλιο εδώ', + 'comment_count' => '{0} Κανένα σχόλιο{1} 1 Σχόλιο [2,*] :count Σχόλια', + 'comment_save' => 'Αποθήκευση Σχολίου', + 'comment_saving' => 'Αποθήκευση σχολίου...', + 'comment_deleting' => 'Διαγραφή σχολίου...', + 'comment_new' => 'Νέο Σχόλιο', + 'comment_created' => 'σχολίασε :createDiff', + 'comment_updated' => 'Ενημερώθηκε :updateDiff από :username', + 'comment_deleted_success' => 'Σχόλιο διαγράφηκε', + 'comment_created_success' => 'Το σχόλιο προστέθηκε', + 'comment_updated_success' => 'Το σχόλιο ενημερώθηκε', + 'comment_delete_confirm' => 'Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό το σχόλιο;', + 'comment_in_reply_to' => 'Σε απάντηση στο :commentId', + + // Revision + 'revision_delete_confirm' => 'Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτήν την αναθεώρηση;', + 'revision_restore_confirm' => 'Είστε βέβαιοι ότι θέλετε να επαναφέρετε αυτή την αναθεώρηση; Τα τρέχοντα περιεχόμενα της σελίδας θα αντικατασταθούν.', + 'revision_delete_success' => 'Η Αναθεώρηση διαγράφηκε', + 'revision_cannot_delete_latest' => 'Δεν είναι δυνατή η διαγραφή της τελευταίας αναθεώρησης.', + + // Copy view + 'copy_consider' => 'Παρακαλώ σκεφτείτε τα παρακάτω κατά την αντιγραφή περιεχομένου.', + 'copy_consider_permissions' => 'Οι ρυθμίσεις προσαρμοσμένων δικαιωμάτων δεν θα αντιγραφούν.', + 'copy_consider_owner' => 'Θα γίνετε ο ιδιοκτήτης όλου του αντιγραμμένου περιεχομένου.', + 'copy_consider_images' => 'Τα αρχεία εικόνας σελίδας δεν θα αναπαραχθούν και οι αρχικές εικόνες θα διατηρήσουν τη σχέση τους με τη σελίδα στην οποία είχαν αρχικά μεταφορτωθεί.', + 'copy_consider_attachments' => 'Τα συνημμένα σελίδας δεν θα αντιγραφούν.', + 'copy_consider_access' => 'Μια αλλαγή της θέσης, του ιδιοκτήτη ή των δικαιωμάτων μπορεί να έχει ως αποτέλεσμα το περιεχόμενο αυτό να είναι προσβάσιμο σε χρήστες που προηγουμένως δεν είχαν πρόσβαση.', + + // Conversions + 'convert_to_shelf' => 'Μετατροπή σε ράφι', + 'convert_to_shelf_contents_desc' => 'Μπορείτε να μετατρέψετε αυτό το βιβλίο σε ένα νέο ράφι με το ίδιο περιεχόμενο. Κεφάλαια που περιέχονται σε αυτό το βιβλίο θα μετατραπούν σε νέα βιβλία. Αν αυτό το βιβλίο περιέχει σελίδες που δεν βρίσκονται σε κεφάλαιο, αυτό το βιβλίο θα μετονομαστεί και περιέχει τέτοιες σελίδες, και αυτό το βιβλίο θα γίνει μέρος του νέου ράφι.', + 'convert_to_shelf_permissions_desc' => 'Τυχόν δικαιώματα που ορίζονται σε αυτό το βιβλίο θα αντιγραφούν στο νέο ράφι και σε όλα τα νέα θυγατρικά βιβλία στα οποία δεν επιβάλλονται τα δικά τους δικαιώματα. Λάβετε υπόψη ότι τα δικαιώματα στα ράφια δεν μεταφέρονται αυτόματα σε περιεχόμενο εντός, όπως συμβαίνει για τα βιβλία.', + 'convert_book' => 'Μετατροπή Βιβλίου', + 'convert_book_confirm' => 'Είστε σίγουροι ότι θέλετε να μετατρέψετε αυτό το βιβλίο;', + 'convert_undo_warning' => 'Αυτό δεν μπορεί να αναιρεθεί τόσο εύκολα.', + 'convert_to_book' => 'Μετατροπή σε βιβλίο', + 'convert_to_book_desc' => 'Μπορείτε να μετατρέψετε αυτό το κεφάλαιο σε ένα νέο βιβλίο με το ίδιο περιεχόμενο. Τυχόν δικαιώματα που έχουν οριστεί σε αυτό το κεφάλαιο θα αντιγραφούν στο νέο βιβλίο αλλά τυχόν κληρονομημένα δικαιώματα, από το γονικό βιβλίο, δεν θα αντιγραφούν το οποίο θα μπορούσε να οδηγήσει σε αλλαγή του ελέγχου πρόσβασης.', + 'convert_chapter' => 'Μετατροπή Κεφαλαίου', + 'convert_chapter_confirm' => 'Είστε βέβαιοι ότι θέλετε να μετατρέψετε αυτό το κεφάλαιο;', + + // References + 'references' => 'Αναφορές', + 'references_none' => 'Δεν υπάρχουν αναφορές παρακολούθησης σε αυτό το στοιχείο.', + 'references_to_desc' => 'Παρακάτω εμφανίζονται όλες οι γνωστές σελίδες του συστήματος που συνδέονται με αυτό το στοιχείο.', +]; diff --git a/resources/lang/el/errors.php b/resources/lang/el/errors.php new file mode 100644 index 000000000..6c489d360 --- /dev/null +++ b/resources/lang/el/errors.php @@ -0,0 +1,109 @@ + 'Δεν έχετε δικαίωμα πρόσβασης στη ζητούμενη σελίδα.', + 'permissionJson' => 'Δεν έχετε άδεια να εκτελέσετε την αιτούμενη ενέργεια.', + + // Auth + 'error_user_exists_different_creds' => 'Ένας χρήστης με email :email υπάρχει ήδη αλλά με διαφορετικά διαπιστευτήρια.', + 'email_already_confirmed' => 'Το email έχει ήδη επιβεβαιωθεί, Δοκιμάστε να συνδεθείτε.', + 'email_confirmation_invalid' => 'Αυτό το διακριτικό επιβεβαίωσης δεν είναι έγκυρο ή έχει ήδη χρησιμοποιηθεί, Παρακαλώ δοκιμάστε να εγγραφείτε ξανά.', + 'email_confirmation_expired' => 'Το διακριτικό επιβεβαίωσης έχει λήξει, έχει σταλεί ένα νέο email επιβεβαίωσης.', + 'email_confirmation_awaiting' => 'Η διεύθυνση ηλεκτρονικού ταχυδρομείου για το λογαριασμό που χρησιμοποιείται πρέπει να επιβεβαιωθεί', + 'ldap_fail_anonymous' => 'Η πρόσβαση LDAP απέτυχε με ανώνυμη σύνδεση', + 'ldap_fail_authed' => 'Η πρόσβαση LDAP απέτυχε με τη χρήση δοσμένων λεπτομερειών dn & κωδικού πρόσβασης', + 'ldap_extension_not_installed' => 'Η επέκταση LDAP PHP δεν εγκαταστάθηκε', + 'ldap_cannot_connect' => 'Αδυναμία σύνδεσης στο διακομιστή ldap, η αρχική σύνδεση απέτυχε', + 'saml_already_logged_in' => 'Ήδη συνδεδεμένος', + 'saml_user_not_registered' => 'Ο χρήστης :name δεν είναι εγγεγραμμένος και η αυτόματη εγγραφή είναι απενεργοποιημένη', + 'saml_no_email_address' => 'Δεν ήταν δυνατή η εύρεση μιας διεύθυνσης ηλεκτρονικού ταχυδρομείου, για αυτόν τον χρήστη, στα δεδομένα που παρέχονται από το εξωτερικό σύστημα ελέγχου ταυτότητας', + 'saml_invalid_response_id' => 'Το αίτημα από το εξωτερικό σύστημα ελέγχου ταυτότητας δεν αναγνωρίζεται από μια διαδικασία που ξεκίνησε από αυτή την εφαρμογή. Η πλοήγηση πίσω μετά από μια σύνδεση θα μπορούσε να προκαλέσει αυτό το ζήτημα.', + 'saml_fail_authed' => 'Η σύνδεση με τη χρήση :system απέτυχε, το σύστημα δεν παρείχε επιτυχή εξουσιοδότηση', + 'oidc_already_logged_in' => 'Ήδη συνδεδεμένος', + 'oidc_user_not_registered' => 'Ο χρήστης :name δεν είναι εγγεγραμμένος και η αυτόματη εγγραφή είναι απενεργοποιημένη', + 'oidc_no_email_address' => 'Δεν ήταν δυνατή η εύρεση μιας διεύθυνσης ηλεκτρονικού ταχυδρομείου, για αυτόν τον χρήστη, στα δεδομένα που παρέχονται από το εξωτερικό σύστημα ελέγχου ταυτότητας', + 'oidc_fail_authed' => 'Η σύνδεση με τη χρήση :system απέτυχε, το σύστημα δεν παρείχε επιτυχή εξουσιοδότηση', + 'social_no_action_defined' => 'Καμία ενέργεια δεν ορίστηκε', + 'social_login_bad_response' => "Παρουσιάστηκε σφάλμα κατά τη διάρκεια :socialAccount login: \n:error", + 'social_account_in_use' => 'Αυτός ο λογαριασμός :socialAccount είναι ήδη σε χρήση, Δοκιμάστε να συνδεθείτε μέσω της επιλογής :socialAccount .', + 'social_account_email_in_use' => 'Το email :email είναι ήδη σε χρήση. Αν έχετε ήδη ένα λογαριασμό, μπορείτε να συνδέσετε τον :socialAccount λογαριασμό σας από τις ρυθμίσεις του προφίλ σας.', + 'social_account_existing' => 'Αυτός ο :socialAccount είναι ήδη συνδεδεμένος στο προφίλ σας.', + 'social_account_already_used_existing' => 'Αυτός ο :socialAccount λογαριασμός χρησιμοποιείται ήδη από άλλο χρήστη.', + 'social_account_not_used' => 'Αυτός ο :socialAccount λογαριασμός δεν είναι συνδεδεμένος με κανέναν χρήστη. Παρακαλώ επισυνάψτε τον στις ρυθμίσεις του προφίλ σας. ', + 'social_account_register_instructions' => 'Εάν δεν έχετε ακόμα λογαριασμό, μπορείτε να καταχωρήσετε ένα λογαριασμό χρησιμοποιώντας την επιλογή :socialAccount .', + 'social_driver_not_found' => 'Δεν βρέθηκε κοινωνικός οδηγός', + 'social_driver_not_configured' => 'Οι κοινωνικές ρυθμίσεις του :socialAccount δεν έχουν ρυθμιστεί σωστά.', + 'invite_token_expired' => 'Αυτός ο σύνδεσμος πρόσκλησης έχει λήξει. Αντ\' αυτού μπορείτε να προσπαθήσετε να επαναφέρετε τον κωδικό πρόσβασής σας.', + + // System + 'path_not_writable' => 'Η διαδρομή αρχείου :filePath δεν μπόρεσε να μεταφορτωθεί. Βεβαιωθείτε ότι είναι εγγράψιμη στο διακομιστή.', + 'cannot_get_image_from_url' => 'Αδυναμία λήψης εικόνας από :url', + 'cannot_create_thumbs' => 'Ο διακομιστής δεν μπορεί να δημιουργήσει μικρογραφίες. Παρακαλώ ελέγξτε ότι έχετε την επέκταση GD PHP εγκατεστημένη.', + 'server_upload_limit' => 'Ο διακομιστής δεν επιτρέπει τη μεταφόρτωση αυτού του μεγέθους. Παρακαλώ δοκιμάστε ένα μικρότερο μέγεθος αρχείου.', + 'uploaded' => 'Ο διακομιστής δεν επιτρέπει τη μεταφόρτωση αυτού του μεγέθους. Παρακαλώ δοκιμάστε ένα μικρότερο μέγεθος αρχείου.', + 'image_upload_error' => 'Παρουσιάστηκε σφάλμα κατά το ανέβασμα της εικόνας.', + 'image_upload_type_error' => 'Ο τύπος εικόνας που μεταφορτώθηκε δεν είναι έγκυρος', + 'file_upload_timeout' => 'Το χρονικό όριο μεταφόρτωσης αρχείου έληξε.', + + // Attachments + 'attachment_not_found' => 'Το συνημμένο δεν βρέθηκε', + + // Pages + 'page_draft_autosave_fail' => 'Αποτυχία αποθήκευσης προσχέδιου. Βεβαιωθείτε ότι έχετε σύνδεση στο διαδίκτυο πριν την αποθήκευση αυτής της σελίδας', + 'page_custom_home_deletion' => 'Δεν μπορεί να διαγραφεί μια σελίδα ενώ έχει οριστεί ως αρχική σελίδα', + + // Entities + 'entity_not_found' => 'Η οντότητα δεν βρέθηκε', + 'bookshelf_not_found' => 'Το ράφι δεν βρέθηκε', + 'book_not_found' => 'Το βιβλίο δεν βρέθηκε', + 'page_not_found' => 'Η σελίδα δεν βρέθηκε', + 'chapter_not_found' => 'Το κεφάλαιο δεν βρέθηκε', + 'selected_book_not_found' => 'Το επιλεγμένο βιβλίο δεν βρέθηκε', + 'selected_book_chapter_not_found' => 'Το επιλεγμένο βιβλίο ή κεφάλαιο δεν βρέθηκε', + 'guests_cannot_save_drafts' => 'Οι επισκέπτες δεν μπορούν να αποθηκεύσουν πρόχειρα', + + // Users + 'users_cannot_delete_only_admin' => 'Δεν μπορείτε να διαγράψετε τον μοναδικό διαχειριστή', + 'users_cannot_delete_guest' => 'Δεν μπορείτε να διαγράψετε τον επισκέπτη', + + // Roles + 'role_cannot_be_edited' => 'Αυτός ο ρόλος δεν μπορεί να επεξεργαστεί', + 'role_system_cannot_be_deleted' => 'Αυτός ο ρόλος είναι ρόλος συστήματος και δεν μπορεί να διαγραφεί', + 'role_registration_default_cannot_delete' => 'Αυτός ο ρόλος δεν μπορεί να διαγραφεί ενώ έχει οριστεί ως προεπιλεγμένος ρόλος εγγραφής', + 'role_cannot_remove_only_admin' => 'Αυτός ο χρήστης είναι ο μόνος χρήστης που έχει ανατεθεί στον ρόλο διαχειριστή. Εκχωρήστε τον ρόλο διαχειριστή σε άλλο χρήστη πριν επιχειρήσετε να τον καταργήσετε εδώ.', + + // Comments + 'comment_list' => 'Παρουσιάστηκε σφάλμα κατά την λήψη σχολίων.', + 'cannot_add_comment_to_draft' => 'Δεν μπορείτε να προσθέσετε σχόλια σε ένα προσχέδιο.', + 'comment_add' => 'Παρουσιάστηκε σφάλμα κατά την προσθήκη / ενημέρωση του σχολίου.', + 'comment_delete' => 'Παρουσιάστηκε σφάλμα κατά τη διαγραφή του σχολίου.', + 'empty_comment' => 'Αδυναμία προσθήκης ενός κενού σχολίου.', + + // Error pages + '404_page_not_found' => 'Η Σελίδα δε βρέθηκε', + 'sorry_page_not_found' => 'Λυπούμαστε, Η σελίδα που αναζητάτε δεν βρέθηκε.', + 'sorry_page_not_found_permission_warning' => 'Αν περιμένατε να υπάρχει αυτή η σελίδα, ίσως να μην έχετε δικαίωμα να την δείτε.', + 'image_not_found' => 'Η Εικόνα δεν βρέθηκε', + 'image_not_found_subtitle' => 'Λυπούμαστε, το αρχείο εικόνας που αναζητάτε δεν μπορεί να βρεθεί.', + 'image_not_found_details' => 'Αν περιμένατε να υπάρχει αυτή η εικόνα, ίσως να έχει διαγραφεί.', + 'return_home' => 'Επιστροφή στην αρχική σελίδα', + 'error_occurred' => 'Προέκυψε Ένα Σφάλμα', + 'app_down' => ':appName είναι προσωρινά μη διαθέσιμη', + 'back_soon' => 'Θα υπάρξει σύντομα υποστήριξη.', + + // API errors + 'api_no_authorization_found' => 'Δεν βρέθηκε διακριτικό εξουσιοδότησης κατόπιν αιτήματος', + 'api_bad_authorization_format' => 'Ένα διακριτικό εξουσιοδότησης βρέθηκε κατόπιν αιτήματος, αλλά η μορφή εμφανίστηκε εσφαλμένη', + 'api_user_token_not_found' => 'Δεν βρέθηκε αντίστοιχο διακριτικό API για το παρεχόμενο διακριτικό εξουσιοδότησης', + 'api_incorrect_token_secret' => 'Το μυστικό που παρέχεται για το δεδομένο χρησιμοποιημένο διακριτικό API είναι εσφαλμένο', + 'api_user_no_api_permission' => 'Ο ιδιοκτήτης του χρησιμοποιημένου διακριτικού API δεν έχει άδεια για να κάνει κλήσεις API', + 'api_user_token_expired' => 'Το διακριτικό εξουσιοδότησης που χρησιμοποιείται έχει λήξει', + + // Settings & Maintenance + 'maintenance_test_email_failure' => 'Σφάλμα κατά την αποστολή δοκιμαστικού email:', + +]; diff --git a/resources/lang/el/pagination.php b/resources/lang/el/pagination.php new file mode 100644 index 000000000..061ad3f11 --- /dev/null +++ b/resources/lang/el/pagination.php @@ -0,0 +1,12 @@ + '« Προηγούμενο', + 'next' => 'Επόμενο »', + +]; diff --git a/resources/lang/el/passwords.php b/resources/lang/el/passwords.php new file mode 100644 index 000000000..a56d1d181 --- /dev/null +++ b/resources/lang/el/passwords.php @@ -0,0 +1,15 @@ + 'Ο κωδικός πρόσβασης πρέπει να αποτελείται από τουλάχιστον έξι χαρακτήρες και να ταιριάζει με τον κωδικό επιβεβαίωσης.', + 'user' => "Δεν μπορούμε να βρόυμε κάποιον χρήστη με αυτή τη διεύθυνση e-mail.", + 'token' => 'Το διακριτικό επαναφοράς κωδικού πρόσβασης δεν είναι έγκυρο για αυτή τη διεύθυνση ηλεκτρονικού ταχυδρομείου.', + 'sent' => 'Σας έχουμε στείλει e-mail με τον σύνδεσμο επαναφοράς του κωδικού πρόσβασης!', + 'reset' => 'Ο κωδικός σας έχει επαναφερθεί!', + +]; diff --git a/resources/lang/el/settings.php b/resources/lang/el/settings.php new file mode 100644 index 000000000..0cc0be262 --- /dev/null +++ b/resources/lang/el/settings.php @@ -0,0 +1,315 @@ + 'Ρυθμίσεις', + 'settings_save' => 'Αποθήκευση ρυθμίσεων', + 'settings_save_success' => 'Οι ρυθμίσεις αποθηκεύτηκαν', + 'system_version' => 'Έκδοση εφαρμογής', + 'categories' => 'Κατηγορίες', + + // App Settings + 'app_customization' => 'Προσαρμογή', + 'app_features_security' => 'Χαρακτηριστικά & Ασφάλεια', + 'app_name' => 'Όνομα Εφαρμογής', + 'app_name_desc' => 'Αυτό το όνομα εμφανίζεται στην κεφαλίδα της ιστοσελίδας και σε τυχόν μηνύματα ηλεκτρονικού ταχυδρομείου που αποστέλλονται από το σύστημα.', + 'app_name_header' => 'Εμφάνιση Ονόματος στην κεφαλίδα', + 'app_public_access' => 'Δημόσια Πρόσβαση', + 'app_public_access_desc' => 'Η ενεργοποίηση αυτής της επιλογής θα επιτρέψει στους επισκέπτες, που δεν είναι συνδεδεμένοι, να έχουν πρόσβαση στο περιεχόμενο της εφαρμογής BookStack.', + 'app_public_access_desc_guest' => 'Η πρόσβαση για δημόσιους επισκέπτες μπορεί να ελεγχθεί μέσω του χρήστη "Guest".', + 'app_public_access_toggle' => 'Να επιτρέπεται η δημόσια πρόσβαση', + 'app_public_viewing' => 'Να επιτρέπεται η δημόσια προβολή;', + 'app_secure_images' => 'Μεταφορτώσεις Εικόνων υψηλότερης Ασφάλειας', + 'app_secure_images_toggle' => 'Ενεργοποιήστε τις μεταφορτώσεις Εικόνων υψηλότερης Ασφάλειας', + 'app_secure_images_desc' => 'Για λόγους απόδοσης, όλες οι εικόνες είναι δημόσιες. Αυτή η επιλογή προσθέτει μια τυχαία συμβολοσειρά μπροστά από τις διευθύνσεις URL εικόνων, δύσκολο να τη μαντέψει κάποιος. Βεβαιωθείτε ότι τα ευρετήρια καταλόγου δεν είναι ενεργοποιημένα για να αποτρέψετε την εύκολη πρόσβαση.', + 'app_default_editor' => 'Προεπιλεγμένος Επεξεργαστής σελίδων', + 'app_default_editor_desc' => 'Επιλέξτε ποιο πρόγραμμα επεξεργασίας θα χρησιμοποιείται από προεπιλογή κατά την επεξεργασία νέων σελίδων. Αυτό μπορεί να παρακαμφθεί σε επίπεδο σελίδας όπου το επιτρέπουν τα δικαιώματα.', + 'app_custom_html' => 'Προσαρμοσμένο περιεχόμενο κεφαλίδας HTML', + 'app_custom_html_desc' => 'Οποιοδήποτε περιεχόμενο προστίθεται εδώ θα εισαχθεί στο κάτω μέρος της ενότητας κάθε σελίδας. Αυτό είναι βολικό για την παράκαμψη ή προσθήκη στυλ καθώς και την προσθήκη κώδικα αναλυτικών στοιχείων.', + 'app_custom_html_disabled_notice' => 'Το προσαρμοσμένο περιεχόμενο κεφαλίδας HTML είναι απενεργοποιημένο σε αυτήν τη σελίδα ρυθμίσεων, για να διασφαλιστεί ότι τυχόν αλλαγές που θα πραγματοποιηθούν και θα προκαλέσουν δυσλειτουργία στην ιστοσελίδα σας, μπορούν να επαναφερθούν.', + 'app_logo' => 'Λογότυπο εφαρμογής', + 'app_logo_desc' => 'Αυτή η εικόνα πρέπει να έχει μέγιστο ύψος 43px.
Οι μεγάλες εικόνες θα μειωθούν.', + 'app_primary_color' => 'Βασικό, χρώμα εφαρμογής', + 'app_primary_color_desc' => 'Ορίζει το κύριο χρώμα για την εφαρμογή, συμπεριλαμβανομένων του banner, των κουμπιών και των συνδέσμων.', + 'app_homepage' => 'Αρχική σελίδα εφαρμογής', + 'app_homepage_desc' => 'Επιλέξτε μια προβολή για εμφάνιση στην αρχική σελίδα αντί για την προεπιλεγμένη προβολή. Τα δικαιώματα σελίδων αγνοούνται για επιλεγμένες σελίδες.', + 'app_homepage_select' => 'Επιλέξτε μια σελίδα', + 'app_footer_links' => 'Σύνδεσμοι υποσέλιδου', + 'app_footer_links_desc' => 'Προσθέστε συνδέσμους για εμφάνιση στο υποσέλιδο του ιστότοπου. Αυτά θα εμφανίζονται στο κάτω μέρος των περισσότερων σελίδων, συμπεριλαμβανομένων εκείνων που δεν απαιτούν σύνδεση. Μπορείτε να χρησιμοποιήσετε μια ετικέτα "trans::" για να χρησιμοποιήσετε μεταφράσεις που καθορίζονται από το σύστημα. Για παράδειγμα: Η χρήση του "trans::common.privacy_policy" θα παρέχει το μεταφρασμένο κείμενο "Πολιτική Απορρήτου" και το "trans::common.terms_of_service" θα παρέχει το μεταφρασμένο κείμενο "Όροι Παροχής Υπηρεσιών".', + 'app_footer_links_label' => 'Ετικέτα Συνδέσμου', + 'app_footer_links_url' => 'URL Σύνδεσης', + 'app_footer_links_add' => 'Προσθήκη Συνδέσμου υποσέλιδου', + 'app_disable_comments' => 'Απενεργοποίηση Σχολίων', + 'app_disable_comments_toggle' => 'Απενεργοποίηση Σχολίων', + 'app_disable_comments_desc' => 'Απενεργοποιεί τα σχόλια σε όλες τις σελίδες της εφαρμογής.
Τα υπάρχοντα σχόλια δεν εμφανίζονται.', + + // Color settings + 'content_colors' => 'Χρώματα εφαρμογής', + 'content_colors_desc' => 'Ορίζει τα χρώματα για όλα τα στοιχεία στην ιεραρχία οργάνωσης της ιστοσελίδας.
Συνιστάται η επιλογή χρωμάτων με παρόμοια φωτεινότητα με τα προεπιλεγμένα χρώματα για αναγνωσιμότητα.', + 'bookshelf_color' => 'Χρώμα Ραφιού', + 'book_color' => 'Χρώμα Βιβλίων', + 'chapter_color' => 'Χρώμα Κεφαλαίων Βιβλίων', + 'page_color' => 'Χρώμα Σελίδων', + 'page_draft_color' => 'Χρώμα Πρoσχέδιων Σελίδων (Draft page)', + + // Registration Settings + 'reg_settings' => 'Εγγραφή', + 'reg_enable' => 'Ενεργοποίηση Εγγραφής', + 'reg_enable_toggle' => 'Ενεργοποίηση εγγραφής', + 'reg_enable_desc' => 'Όταν ενεργοποιηθεί η εγγραφή, ο χρήστης θα μπορεί να εγγραφεί ως χρήστης της εφαρμογής. Κατά την εγγραφή τους δίνεται ένας μοναδικός, προεπιλεγμένος ρόλος χρήστη.', + 'reg_default_role' => 'Προεπιλεγμένος ρόλος χρήστη μετά την εγγραφή', + 'reg_enable_external_warning' => 'Η παραπάνω επιλογή αγνοείται όταν ο εξωτερικός έλεγχος ταυτότητας LDAP ή SAML είναι ενεργός. Οι λογαριασμοί χρηστών για μη υπάρχοντα μέλη θα δημιουργηθούν αυτόματα εάν ο έλεγχος ταυτότητας, έναντι του εξωτερικού συστήματος που χρησιμοποιείται, είναι επιτυχής.', + 'reg_email_confirmation' => 'Επιβεβαίωση ηλεκτρονικού ταχυδρομείου', + 'reg_email_confirmation_toggle' => 'Απαιτείται η επιβεβαίωση μέσω email', + 'reg_confirm_email_desc' => 'Εάν χρησιμοποιείται περιορισμός τομέα, τότε θα απαιτείται επιβεβαίωση μέσω email και αυτή η επιλογή θα αγνοηθεί.', + 'reg_confirm_restrict_domain' => 'Περιορισμός Τομέα', + 'reg_confirm_restrict_domain_desc' => 'Εισαγάγετε μια λίστα διαχωρισμένων με κόμματα τομέων email στους οποίους θέλετε να περιορίσετε την εγγραφή. Θα σταλεί στους χρήστες ένα email για να επιβεβαιώσουν τη διεύθυνσή τους πριν τους επιτραπεί να αλληλεπιδράσουν με την εφαρμογή.
Σημειώστε ότι οι χρήστες θα μπορούν να αλλάξουν τις διευθύνσεις email τους μετά την επιτυχή εγγραφή.', + 'reg_confirm_restrict_domain_placeholder' => 'Δε έχουν ρυθμιστεί περιορισμοί ακόμα', + + // Maintenance settings + 'maint' => 'Συντήρηση', + 'maint_image_cleanup' => 'Εκκαθάριση Εικόνων', + 'maint_image_cleanup_desc' => 'Σαρώνει το περιεχόμενο σελίδων και τις αναθεωρήσεις αυτών για να ελέγξει ποιες εικόνες και σχέδια χρησιμοποιούνται αυτήν τη στιγμή και ποιες είναι περιττές. Βεβαιωθείτε ότι έχετε δημιουργήσει ένα πλήρες αντίγραφο της βάση δεδομένων και των εικόνων προτού το εκτελέσετε.', + 'maint_delete_images_only_in_revisions' => 'Διαγράψτε επίσης εικόνες που υπάρχουν μόνο σε παλιές αναθεωρήσεις σελίδων', + 'maint_image_cleanup_run' => 'Εκτέλεση Εκκαθάρισης', + 'maint_image_cleanup_warning' => 'Βρέθηκαν :count δυνητικά αχρησιμοποίητες εικόνες. Είστε βέβαιοι ότι θέλετε να τις διαγράψετε αυτές;', + 'maint_image_cleanup_success' => ':count δυνητικά αχρησιμοποίητες εικόνες βρέθηκαν και διαγράφηκαν!', + 'maint_image_cleanup_nothing_found' => 'Δεν βρέθηκαν αχρησιμοποίητες εικόνες, τίποτα δεν διαγράφηκε!', + 'maint_send_test_email' => 'Στείλτε ένα δοκιμαστικό email', + 'maint_send_test_email_desc' => 'Αυτό στέλνει ένα δοκιμαστικό μήνυμα ηλεκτρονικού ταχυδρομείου στη διεύθυνση email σας που προσδιορίζεται στο προφίλ σας.', + 'maint_send_test_email_run' => 'Αποστολή δοκιμαστικού email', + 'maint_send_test_email_success' => 'Το email στάλθηκε στη διεύθυνση :address', + 'maint_send_test_email_mail_subject' => 'Δοκιμαστικό Email', + 'maint_send_test_email_mail_greeting' => 'Η παράδοση email φαίνεται να λειτουργεί!', + 'maint_send_test_email_mail_text' => 'Συγχαρητήρια! Καθώς λάβατε αυτήν την ειδοποίηση μέσω email, οι ρυθμίσεις email σας φαίνεται να έχουν διαμορφωθεί σωστά.', + 'maint_recycle_bin_desc' => 'Τα διαγραμμένα Ράφια και βιβλία, τα διαγραμμένα κεφάλαια και σελίδες αποστέλλονται στον κάδο ανακύκλωσης, έτσι ώστε να μπορούν να αποκατασταθούν ή να διαγραφούν οριστικά. Τα παλαιότερα αντικείμενα στον κάδο ανακύκλωσης ενδέχεται να αφαιρεθούν αυτόματα μετά από λίγο, ανάλογα με τη διαμόρφωση του συστήματος.', + 'maint_recycle_bin_open' => 'Άνοιγμα Κάδου Ανακύκλωσης', + 'maint_regen_references' => 'Αναδημιουργία Αναφορών', + 'maint_regen_references_desc' => 'Αυτή η ενέργεια θα ξαναχτίσει το ευρετήριο αναφοράς διαστοιχείου μέσα στη βάση δεδομένων. Αυτό συνήθως γίνεται αυτόματα αλλά αυτή η ενέργεια μπορεί να είναι χρήσιμη για το παλιό περιεχόμενο ή περιεχόμενο που προστίθεται μέσω ανεπίσημων μεθόδων.', + 'maint_regen_references_success' => 'Το ευρετήριο αναφοράς αναδημιουργήθηκε!', + 'maint_timeout_command_note' => 'Σημείωση: Αυτή η ενέργεια μπορεί να πάρει χρόνο για να εκτελεστεί, η οποία μπορεί να οδηγήσει σε προβλήματα χρονικού ορίου σε ορισμένα περιβάλλοντα ιστού. Ως εναλλακτική λύση, αυτή η ενέργεια πρέπει να εκτελείται χρησιμοποιώντας μια εντολή τερματικού.', + + // Recycle Bin + 'recycle_bin' => 'Κάδος Ανακύκλωσης', + 'recycle_bin_desc' => 'Εδώ μπορείτε να επαναφέρετε στοιχεία που έχουν διαγραφεί ή να επιλέξετε να τα αφαιρέσετε οριστικά από το σύστημα. Αυτή η λίστα δεν είναι φιλτραρισμένη όπως γίνεται σε παρόμοιες λίστες δραστηριοτήτων στο σύστημα στις οποίες εφαρμόζονται φίλτρα αδειών.', + 'recycle_bin_deleted_item' => 'Διαγραμμένο στοιχείο', + 'recycle_bin_deleted_parent' => 'Γονικό Στοιχείο', + 'recycle_bin_deleted_by' => 'Διαγράφηκε από', + 'recycle_bin_deleted_at' => 'Ημ/νια - Ώρα Διαγραφής', + 'recycle_bin_permanently_delete' => 'Οριστική Διαγραφή', + 'recycle_bin_restore' => 'Επαναφορά', + 'recycle_bin_contents_empty' => 'Ο κάδος ανακύκλωσης είναι επί του παρόντος άδειος', + 'recycle_bin_empty' => 'Αδειάστε τον Κάδο Ανακύκλωσης', + 'recycle_bin_empty_confirm' => 'Αυτό θα καταστρέψει οριστικά όλα τα αντικείμενα στον κάδο ανακύκλωσης, συμπεριλαμβανομένου του περιεχομένου που περιέχεται σε κάθε αντικείμενο. Είστε βέβαιοι ότι θέλετε να αδειάσετε τον κάδο ανακύκλωσης;', + 'recycle_bin_destroy_confirm' => 'Αυτή η ενέργεια θα διαγράψει οριστικά από το σύστημα αυτό το στοιχείο μαζί με τυχόν θυγατρικά, που αναφέρονται παρακάτω. Μετά την επιβεβαίωση της διαγραφής δεν θα μπορείτε να επαναφέρετε αυτό το περιεχόμενο. Είστε βέβαιοι ότι θέλετε να διαγράψετε οριστικά αυτό το στοιχείο;', + 'recycle_bin_destroy_list' => 'Αντικείμενα για καταστροφή', + 'recycle_bin_restore_list' => 'Αντικείμενα για επαναφορά', + 'recycle_bin_restore_confirm' => 'Αυτή η ενέργεια θα επαναφέρει το διαγραμμένο στοιχείο, συμπεριλαμβανομένων τυχόν θυγατρικών στοιχείων, στην αρχική τους θέση. Εάν η αρχική τοποθεσία έχει από τότε διαγραφεί και βρίσκεται τώρα στον κάδο ανακύκλωσης, θα πρέπει επίσης να αποκατασταθεί και το γονικό στοιχείο.', + 'recycle_bin_restore_deleted_parent' => 'Το γονικό στοιχείο αυτού του στοιχείου έχει επίσης διαγραφεί. Αυτά θα παραμείνουν διαγραμμένα μέχρι να αποκατασταθεί και αυτός ο γονέας.', + 'recycle_bin_restore_parent' => 'Επαναφορά Γονέα', + 'recycle_bin_destroy_notification' => 'Διαγράφηκαν :count συνολικά αντικείμενα από τον κάδο ανακύκλωσης.', + 'recycle_bin_restore_notification' => 'Επαναφέρθηκαν :count συνολικά αντικείμενα από τον κάδο ανακύκλωσης.', + + // Audit Log + 'audit' => 'Αρχείο Καταγραφής', + 'audit_desc' => 'Αυτό το αρχείο καταγραφής ελέγχου ενεργειών, εμφανίζει μια λίστα δραστηριοτήτων που παρακολουθούνται στο σύστημα. Αυτή η λίστα δεν είναι φιλτραρισμένη σε αντίθεση με παρόμοιες λίστες δραστηριοτήτων στο σύστημα όπου εφαρμόζονται φίλτρα αδειών.', + 'audit_event_filter' => 'Φίλτρο Συμβάντων', + 'audit_event_filter_no_filter' => 'Χωρίς Φίλτρο', + 'audit_deleted_item' => 'Διαγραμμένο στοιχείο', + 'audit_deleted_item_name' => 'Ονομα: :name', + 'audit_table_user' => 'Χρήστης', + 'audit_table_event' => 'Συμβάν', + 'audit_table_related' => 'Σχετικό Αντικείμενο ή Λεπτομέρεια', + 'audit_table_ip' => 'Διεύθυνση IP', + 'audit_table_date' => 'Ημερομηνία Δραστηριότητας', + 'audit_date_from' => 'Εύρος Ημερομηνίας Από', + 'audit_date_to' => 'Εύρος Ημερομηνίας Έως', + + // Role Settings + 'roles' => 'Ρόλοι', + 'role_user_roles' => 'Ρόλοι Χρηστών', + 'role_create' => 'Δημιουργία νέου ρόλου', + 'role_create_success' => 'Ο Ρόλος δημιουργήθηκε με επιτυχία', + 'role_delete' => 'Διαγραφή Ρόλου', + 'role_delete_confirm' => 'Αυτό θα διαγράψει τον ρόλο με το όνομα \':roleName\'.', + 'role_delete_users_assigned' => 'Σε αυτόν τον ρόλο έχουν εκχωρηθεί :userCount χρήστες. Εάν θέλετε να μετεγκαταστήσετε τους χρήστες από αυτόν τον ρόλο, επιλέξτε έναν νέο ρόλο παρακάτω.', + 'role_delete_no_migration' => "Μην μεταφέρετε χρήστες", + 'role_delete_sure' => 'Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτόν τον ρόλο;', + 'role_delete_success' => 'Ο ρόλος διαγράφηκε επιτυχώς', + 'role_edit' => 'Επεξεργασία Ρόλου', + 'role_details' => 'Λεπτομέρειες Ρόλου', + 'role_name' => 'Όνομα Ρόλου', + 'role_desc' => 'Σύντομη περιγραφή του Ρόλου', + 'role_mfa_enforced' => 'Απαιτεί έλεγχο ταυτότητας πολλαπλών παραγόντων', + 'role_external_auth_id' => 'Εξωτερικά αναγνωριστικά (IDs) ελέγχου ταυτότητας', + 'role_system' => 'Δικαιώματα Συστήματος', + 'role_manage_users' => 'Διαχείριση Χρηστών', + 'role_manage_roles' => 'Διαχείριση Ρόλων και Δικαιωμάτων ρόλων', + 'role_manage_entity_permissions' => 'Διαχειριστείτε όλα τα δικαιώματα βιβλίου, κεφαλαίων και σελίδων', + 'role_manage_own_entity_permissions' => 'Διαχειριστείτε τα δικαιώματα στο δικό σας βιβλίο, κεφάλαιο και σελίδες', + 'role_manage_page_templates' => 'Διαχείριση προτύπων σελίδων', + 'role_access_api' => 'Πρόσβαση στο API του συστήματος', + 'role_manage_settings' => 'Διαχειριστείτε τις ρυθμίσεις του ΑΡΙ', + 'role_export_content' => 'Εξαγωγή περιεχομένου', + 'role_editor_change' => 'Αλλαγή προγράμματος επεξεργασίας σελίδας', + 'role_asset' => 'Δικαιώματα Συστήματος', + 'roles_system_warning' => 'Λάβετε υπόψη ότι η πρόσβαση σε οποιοδήποτε από τις τρεις παραπάνω άδειες (δικαιώματα) μπορεί να επιτρέψει σε έναν χρήστη να αλλάξει τα δικά του προνόμια ή τα προνόμια άλλων στο σύστημα. Εκχωρήστε ρόλους με αυτά τα δικαιώματα μόνο σε αξιόπιστους χρήστες.', + 'role_asset_desc' => 'Αυτά τα δικαιώματα ελέγχουν την προεπιλεγμένη πρόσβαση στα στοιχεία (άδειες) εντός του συστήματος. Τα δικαιώματα σε Βιβλία, Κεφάλαια και Σελίδες θα παρακάμψουν αυτές τις άδειες.', + 'role_asset_admins' => 'Οι διαχειριστές έχουν αυτόματα πρόσβαση σε όλο το περιεχόμενο, αλλά αυτές οι επιλογές ενδέχεται να εμφανίζουν ή να αποκρύπτουν τις επιλογές διεπαφής χρήστη.', + 'role_asset_image_view_note' => 'Αυτό σχετίζεται με την ορατότητα εντός του διαχειριστή εικόνων. Η πραγματική πρόσβαση των μεταφορτωμένων αρχείων εικόνας θα εξαρτηθεί από την επιλογή αποθήκευσης εικόνας συστήματος.', + 'role_all' => 'Ολα', + 'role_own' => 'Τα δικά του', + 'role_controlled_by_asset' => 'Ελέγχονται από το στοιχείο στο οποίο ανεβαίνουν (Ράφια, Βιβλία)', + 'role_save' => 'Αποθήκευση Ρόλου', + 'role_update_success' => 'Ο Ρόλος ενημερώθηκε με επιτυχία', + 'role_users' => 'Χρήστες σε αυτόν τον Ρόλο', + 'role_users_none' => 'Σε κανένα χρήστη δεν έχει ανατεθεί αυτήν τη στιγμή αυτός ο ρόλος.', + + // Users + 'users' => 'Χρήστες', + 'user_profile' => 'Προφίλ Χρήστη', + 'users_add_new' => 'Προσθήκη νέου Χρήστη', + 'users_search' => 'Αναζήτηση Χρηστών', + 'users_latest_activity' => 'Τελευταία Δραστηριότητα', + 'users_details' => 'Στοιχεία χρήστη', + 'users_details_desc' => 'Ορίστε ένα εμφανιζόμενο όνομα και μια διεύθυνση email για αυτόν τον χρήστη. Η διεύθυνση email θα χρησιμοποιηθεί για τη σύνδεση στην εφαρμογή.', + 'users_details_desc_no_email' => 'Ορίστε το όνομα που θα εμφανίζεται για το χρήστη αυτόν, έτσι ώστε να είναι αναγνωρίσιμος από τους υπόλοιπους.', + 'users_role' => 'Ρόλοι χρήστη', + 'users_role_desc' => 'Επιλέξτε σε ποιους ρόλους θα εκχωρηθεί αυτός ο χρήστης. Εάν ένας χρήστης έχει εκχωρηθεί σε πολλούς ρόλους, τα δικαιώματα από αυτούς τους ρόλους θα στοιβάζονται και θα λαμβάνουν όλες τις ικανότητες των ρόλων που έχουν εκχωρηθεί.', + 'users_password' => 'Κωδικός Χρήστη', + 'users_password_desc' => 'Ορίστε έναν κωδικό πρόσβασης που θα χρησιμοποιείται για τη σύνδεση στην εφαρμογή. Αυτός πρέπει να είναι τουλάχιστον 8 χαρακτήρες.', + 'users_send_invite_text' => 'Μπορείτε να επιλέξετε να στείλετε σε αυτόν τον χρήστη ένα email πρόσκλησης που του επιτρέπει να ορίσει τον δικό του κωδικό πρόσβασης. Σε διαφορετική περίπτωση μπορείτε να ορίσετε τον κωδικό πρόσβασής του εσείς.', + 'users_send_invite_option' => 'Αποστολή email πρόσκλησης σε χρήστη', + 'users_external_auth_id' => 'Εξωτερικός έλεγχος ταυτότητας', + 'users_external_auth_id_desc' => 'Αυτό είναι το αναγνωριστικό που χρησιμοποιείται για την αντιστοίχιση αυτού του χρήστη κατά την επικοινωνία με το εξωτερικό σύστημα ελέγχου ταυτότητας.', + 'users_password_warning' => 'Συμπληρώστε τα παρακάτω μόνο αν θέλετε να αλλάξετε τον κωδικό πρόσβασής σας.', + 'users_system_public' => 'Αυτός ο χρήστης αντιπροσωπεύει οποιονδήποτε επισκέπτη που επισκέπτεται τη Βιβλιοθήκη σας. Δεν μπορεί να χρησιμοποιηθεί για τη σύνδεση αλλά εκχωρείται αυτόματα.', + 'users_delete' => 'Διαγραφή Χρήστη', + 'users_delete_named' => 'Διαγραφή χρήστη :userName', + 'users_delete_warning' => 'Αυτό θα διαγράψει πλήρως αυτόν τον χρήστη με το όνομα \':userName\' από το σύστημα.', + 'users_delete_confirm' => 'Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτόν τον χρήστη;', + 'users_migrate_ownership' => 'Μεταφορά ιδιοκτησίας', + 'users_migrate_ownership_desc' => 'Επιλέξτε έναν χρήστη εδώ, εάν θέλετε ένας άλλος χρήστης να γίνει ο κάτοχος όλων των στοιχείων που ανήκουν επί του παρόντος σε αυτόν τον χρήστη.', + 'users_none_selected' => 'Δεν έχει επιλεγεί χρήστης', + 'users_edit' => 'Επεξεργασία Χρήστη', + 'users_edit_profile' => 'Ρυθμίσεις προφίλ', + 'users_avatar' => 'Avatar Χρήστη', + 'users_avatar_desc' => 'Επιλέξτε μια εικόνα που θα αντιπροσωπεύει αυτόν τον χρήστη. Αυτό θα πρέπει να είναι περίπου 256px τετράγωνο.', + 'users_preferred_language' => 'Προτιμώμενη γλώσσα', + 'users_preferred_language_desc' => 'Αυτή η επιλογή θα αλλάξει τη γλώσσα που χρησιμοποιείται για τη διεπαφή χρήστη της εφαρμογής. Αυτό δεν θα επηρεάσει οποιοδήποτε περιεχόμενο που δημιουργήθηκε από χρήστες.', + 'users_social_accounts' => 'Λογαριασμοί Κοινωνικής δικτύωσης ', + 'users_social_accounts_info' => 'Εδώ μπορείτε να συνδέσετε τους άλλους λογαριασμούς σας για ταχύτερη και ευκολότερη σύνδεση. Η αποσύνδεση ενός λογαριασμού εδώ δεν ανακαλεί προηγουμένως εξουσιοδοτημένη πρόσβαση. Ανάκληση πρόσβασης από τις ρυθμίσεις προφίλ σας στον συνδεδεμένο κοινωνικό λογαριασμό.', + 'users_social_connect' => 'Σύνδεση λογαριασμού', + 'users_social_disconnect' => 'Αποσύνδεση λογαριασμού', + 'users_social_connected' => ':socialΛογαριασμός λογαριασμού συνδέθηκε με επιτυχία στο προφίλ σας.', + 'users_social_disconnected' => ':socialΛογαριασμός αποσυνδέθηκε επιτυχώς από το προφίλ σας.', + 'users_api_tokens' => 'API Tokens', + 'users_api_tokens_none' => 'Δεν έχουν δημιουργηθεί διακριτικά API για αυτόν το χρήστη', + 'users_api_tokens_create' => 'Δημιουργία διακριτικού Api Token', + 'users_api_tokens_expires' => 'Λήγει', + 'users_api_tokens_docs' => 'Τεκμηρίωση API', + 'users_mfa' => 'Έλεγχος Ταυτοτητας Πολλαπλων Παραγοντων', + 'users_mfa_desc' => 'Ρυθμίστε τον έλεγχο ταυτότητας πολλαπλών παραγόντων ως ένα επιπλέον επίπεδο ασφάλειας για τον λογαριασμό χρήστη σας.', + 'users_mfa_x_methods' => ':count μέθοδος έχει ρυθμιστεί:count μέθοδοι', + 'users_mfa_configure' => 'Ρύθμιση Μεθόδων', + + // API Tokens + 'user_api_token_create' => 'Δημιουργία διακριτικού (API Token)', + 'user_api_token_name' => 'Όνομα', + 'user_api_token_name_desc' => 'Δώστε στο διακριτικό σας ένα ευανάγνωστο όνομα ως μελλοντική υπενθύμιση του σκοπού του.', + 'user_api_token_expiry' => 'Ημερομηνία λήξης', + 'user_api_token_expiry_desc' => 'Ορίστε μια ημερομηνία κατά την οποία λήγει αυτό το διακριτικό. Μετά από αυτήν την ημερομηνία, τα αιτήματα που γίνονται με αυτό το διακριτικό δεν θα λειτουργούν πλέον. Αν αφήσετε αυτό το πεδίο κενό, θα οριστεί η λήξη 100 χρόνια στο μέλλον.', + 'user_api_token_create_secret_message' => 'Αμέσως μετά τη δημιουργία αυτού του διακριτικού θα δημιουργηθεί και θα εμφανιστεί ένα "Token ID" & "Token Secret". Το μυστικό(Token Secret) θα εμφανιστεί μόνο μία φορά, επομένως φροντίστε να αντιγράψετε την τιμή σε κάποιο ασφαλές μέρος πριν συνεχίσετε.', + 'user_api_token_create_success' => 'Το διακριτικό API δημιουργήθηκε με επιτυχία', + 'user_api_token_update_success' => 'Το διακριτικό API ενημερώθηκε με επιτυχία', + 'user_api_token' => 'API Token', + 'user_api_token_id' => 'Token ID', + 'user_api_token_id_desc' => 'Αυτό είναι ένα μη επεξεργάσιμο αναγνωριστικό που δημιουργείται από το σύστημα για αυτό το διακριτικό, το οποίο θα πρέπει να παρέχεται σε αιτήματα API.', + 'user_api_token_secret' => 'Μυστικό Token', + 'user_api_token_secret_desc' => 'Αυτό είναι ένα μυστικό που δημιουργείται από το σύστημα για αυτό το διακριτικό και θα πρέπει να παρέχεται σε αιτήματα API. Αυτό θα εμφανιστεί μόνο μία φορά, επομένως αντιγράψτε αυτήν την τιμή σε κάποιο ασφαλές μέρος.', + 'user_api_token_created' => 'Το Διακριτικό δημιουργήθηκε :timeAgo', + 'user_api_token_updated' => 'Το Διακριτικό ενημερώθηκε :timeAgo', + 'user_api_token_delete' => 'Διαγραφή Διακριτικού', + 'user_api_token_delete_warning' => 'Αυτό θα διαγράψει πλήρως αυτό το διακριτικό API με το όνομα \':tokenName\' από το σύστημα.', + 'user_api_token_delete_confirm' => 'Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό το διακριτικό API;', + 'user_api_token_delete_success' => 'Το διακριτικό API διαγράφηκε με επιτυχία', + + // Webhooks + 'webhooks' => 'Webhooks', + 'webhooks_create' => 'Δημιουργία νέου Webhook', + 'webhooks_none_created' => 'Δεν έχουν δημιουργηθεί ακόμη webhook.', + 'webhooks_edit' => 'Επεξεργασία Webhook', + 'webhooks_save' => 'Αποθήκευση Webhook', + 'webhooks_details' => 'Λεπτομέρειες Webhook', + 'webhooks_details_desc' => 'Παρέχετε ένα φιλικό προς τον χρήστη όνομα και ένα τελικό σημείο POST ως τοποθεσία για την αποστολή των δεδομένων webhook.', + 'webhooks_events' => 'Συμβάντα Webhook', + 'webhooks_events_desc' => 'Επιλέξτε όλα τα συμβάντα που θα πρέπει να ενεργοποιήσουν αυτό το webhook για κλήση.', + 'webhooks_events_warning' => 'Λάβετε υπόψη ότι αυτά τα συμβάντα θα ενεργοποιηθούν για όλα τα επιλεγμένα συμβάντα, ακόμη και αν εφαρμοστούν προσαρμοσμένα δικαιώματα. Βεβαιωθείτε ότι η χρήση αυτού του webhook δεν θα αποκαλύψει εμπιστευτικό περιεχόμενο.', + 'webhooks_events_all' => 'Όλα τα συμβάντα του συστήματος', + 'webhooks_name' => 'Όνομα Webhook', + 'webhooks_timeout' => 'Χρονικό όριο λήξης αιτήματος Webhook (δευτερόλεπτα)', + 'webhooks_endpoint' => 'Τελικό Σημείο Webhook', + 'webhooks_active' => 'Webhook Ενεργό', + 'webhook_events_table_header' => 'Συμβάντα', + 'webhooks_delete' => 'Διαγραφή Webhook', + 'webhooks_delete_warning' => 'Αυτό θα διαγράψει πλήρως αυτό το webhook, με το όνομα \':webhookName\', από το σύστημα.', + 'webhooks_delete_confirm' => 'Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό το webhook;', + 'webhooks_format_example' => 'Παράδειγμα μορφής Webhook', + 'webhooks_format_example_desc' => 'Τα δεδομένα Webhook αποστέλλονται ως αίτημα POST στο διαμορφωμένο τελικό σημείο ως JSON ακολουθώντας την παρακάτω μορφή. Οι ιδιότητες "related_item" και "url" είναι προαιρετικές και εξαρτώνται από τον τύπο του συμβάντος που ενεργοποιείται.', + 'webhooks_status' => 'Κατάσταση Webhook', + 'webhooks_last_called' => 'Τελευταία κλήση:', + 'webhooks_last_errored' => 'Τελευταίο σφάλμα:', + 'webhooks_last_error_message' => 'Τελευταίο μήνυμα λάθους:', + + + //! 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' => 'العربية', + 'bg' => 'Bǎlgarski', + 'bs' => 'Bosanski', + 'ca' => 'Català', + 'cs' => 'Česky', + 'da' => 'Dansk', + 'de' => 'Deutsch (Sie)', + 'de_informal' => 'Deutsch (Du)', + 'es' => 'Español', + 'es_AR' => 'Español Argentina', + 'et' => 'Eesti keel', + 'eu' => 'Euskara', + 'fa' => 'فارسی', + 'fr' => 'Français', + 'he' => 'עברית', + 'hr' => 'Hrvatski', + 'hu' => 'Magyar', + 'id' => 'Bahasa Indonesia', + 'it' => 'Italian', + 'ja' => '日本語', + 'ko' => '한국어', + 'lt' => 'Lietuvių Kalba', + 'lv' => 'Latviešu Valoda', + 'nl' => 'Nederlands', + 'nb' => 'Norsk (Bokmål)', + 'pl' => 'Polski', + 'pt' => 'Português', + 'pt_BR' => 'Português do Brasil', + 'ro' => 'Română', + 'ru' => 'Русский', + 'sk' => 'Slovensky', + 'sl' => 'Slovenščina', + 'sv' => 'Svenska', + 'tr' => 'Türkçe', + 'uk' => 'Українська', + 'vi' => 'Tiếng Việt', + 'zh_CN' => '简体中文', + 'zh_TW' => '繁體中文', + ], + //!//////////////////////////////// +]; diff --git a/resources/lang/el/validation.php b/resources/lang/el/validation.php new file mode 100644 index 000000000..be96cd0ca --- /dev/null +++ b/resources/lang/el/validation.php @@ -0,0 +1,117 @@ + 'Το :attribute πρέπει να γίνει δεκτό.', + 'active_url' => 'Το :attribute δεν είναι ένα έγκυρο URL.', + 'after' => 'Το :attribute πρέπει να είναι μια ημερομηνία μετά τις :date.', + 'alpha' => 'Το :attribute μπορεί να περιέχει μόνο γράμματα.', + 'alpha_dash' => 'Tο :attribute μπορεί να περιλαμβάνει μόνο γράμματα, αριθμούς, παύλες και κάτω παύλες.', + 'alpha_num' => 'Tο :attribute μπορεί να περιλαμβάνει μόνο γράμματα και αριθμούς.', + 'array' => 'Το :attribute πρέπει να είναι πίνακας.', + 'backup_codes' => 'Ο παρεχόμενος κωδικός δεν είναι έγκυρος ή έχει ήδη χρησιμοποιηθεί.', + 'before' => 'Tο :attribute πρέπει να είναι μια ημερομηνία πριν από :date.', + 'between' => [ + 'numeric' => 'Το :attribute πρέπει να είναι μεταξύ :min και :max.', + 'file' => 'Το :attribute πρέπει να είναι μεταξύ :min και :max kilobytes.', + 'string' => 'Το πεδίο :attribute πρέπει να είναι μεταξύ από :min και :max characters.', + 'array' => 'Το πεδίο :attribute πρέπει να είναι μεταξύ :min και :max αντικείμενα.', + ], + 'boolean' => 'Το πεδίο :attribute πρέπει να είναι σωστό ή λάθος.', + 'confirmed' => 'Η επιβεβαίωση του :attribute δεν ταιριάζει.', + 'date' => 'Το :attribute δεν έχει έγκυρη ημερομηνία.', + 'date_format' => 'Το :attribute δεν ταιριάζει με τη μορφή :format.', + 'different' => 'Τα πεδία :attribute και :other πρέπει να είναι διαφορετικά.', + 'digits' => 'Το πεδίο :attribute πρέπει να είναι :digits ψηφία.', + 'digits_between' => 'To :attribute πρέπει να είναι μεταξύ :min και :max ψηφία.', + 'email' => 'Το πεδίο :attribute πρέπει να είναι μία έγκυρη διεύθυνση E-mail.', + 'ends_with' => 'Το :attribute πρέπει να τελειώνει με μια απο τις ακόλουθες: :values', + 'file' => 'Το :attribute πρέπει να παρέχεται ως έγκυρο αρχείο.', + 'filled' => 'Το πεδίο :attribute είναι υποχρεωτικό.', + 'gt' => [ + 'numeric' => 'Το :attribute πρέπει να είναι μεγαλύτερο από :value.', + 'file' => 'To :attribute πρέπει να είναι μεγαλύτερο από :value kilobytes.', + 'string' => 'Tο :attribute πρέπει να έχει περισσότερους από :value χαρακτήρες.', + 'array' => 'Το :attribute πρέπει να περιέχει περισσότερα από :value αντικείμενα.', + ], + 'gte' => [ + 'numeric' => 'Το :attribute πρέπει να είναι μεγαλύτερο ή ίσο από :value.', + 'file' => 'Το :attribute πρέπει να είναι μεγαλύτερο ή ίσο με :value kilobytes.', + 'string' => 'To :attribute πρέπει να είναι μεγαλύτερο ή ίσο από :value χαρακτήρες.', + 'array' => 'Tο :attribute πρέπει να έχει :value αντικείμενα ή περισσότερα.', + ], + 'exists' => 'Το επιλεγμένο :attribute δεν είναι έγκυρο.', + 'image' => 'Tο :attribute πρέπει να είναι εικόνα.', + 'image_extension' => 'Το πεδίο :attribute πρέπει να έχει μια έγκυρη & υποστηριζόμενη επέκταση εικόνας.', + 'in' => 'Το επιλεγμένο :attribute δεν είναι έγκυρο.', + 'integer' => 'Tο :attribute πρέπει να είναι ακέραιος αριθμός.', + 'ip' => 'Το πεδίο :attribute πρέπει να είναι μία έγκυρη διεύθυνση IP.', + 'ipv4' => 'Tο :attribute πρέπει να είναι μια έγκυρη διεύθυνση IPv4.', + 'ipv6' => 'Tο :attribute πρέπει να είναι μια έγκυρη διεύθυνση IPv6.', + 'json' => 'H :attribute πρεπει να είναι μια έγκυρη συμβολοσειρά JSON.', + 'lt' => [ + 'numeric' => 'Tο :attribute πρέπει να είναι λιγότερο από :value.', + 'file' => 'To :attribute πρέπει να είναι μικρότερο από :value kilobytes.', + 'string' => 'To :attribute πρέπει να είναι μικρότερο από :value kilobytes.', + 'array' => 'Tο :attribute πρέπει να έχει λιγότερα από :value αντικείμενα.', + ], + 'lte' => [ + 'numeric' => 'Το :attribute πρέπει να είναι μικρότερο ή ίσο του :value.', + 'file' => 'Το :attribute πρέπει να είναι μικρότερο ή ίσο του :value kilobytes.', + 'string' => 'Tο :attribute πρέπει να έχει λιγότερους από ή ίδιους :value χαρακτήρες.', + 'array' => 'Tο :attribute δεν πρέπει να έχει περισσότερα από :value αντικείμενα.', + ], + 'max' => [ + 'numeric' => 'Tο :attribute δεν μπορεί να είναι μεγαλύτερο από :max.', + 'file' => 'To :attribute δεν μπορεί να είναι μεγαλύτερο από :max kilobytes.', + 'string' => 'Το :attribute δεν μπορεί να είναι μεγαλύτερο από :max χαρακτήρες.', + 'array' => 'Tο :attribute δεν μπορεί να έχει περισσότερα από :max αντικείμενα.', + ], + 'mimes' => 'Το πεδίο :attribute πρέπει να είναι ένα αρχείου τύπου: :values.', + 'min' => [ + 'numeric' => 'To :attribute πρέπει να είναι τουλάχιστον :min.', + 'file' => 'Το :attribute πρέπει είναι τουλάχιστον :min kilobytes.', + 'string' => 'Το :attribute πρέπει να είναι τουλάχιστον :min χαρακτήρες.', + 'array' => 'To :attribute πρέπει να έχει τουλάχιστον :min αντικείμενα.', + ], + 'not_in' => 'Το επιλεγμένο :attribute δεν είναι έγκυρο.', + 'not_regex' => 'Η μορφή του :attribute δεν είναι έγκυρη.', + 'numeric' => 'To :attribute πρέπει να είναι αριθμός.', + 'regex' => 'Το :attribute έχει μη έγκυρη μορφή.', + 'required' => 'Το πεδίο :attribute είναι υποχρεωτικό.', + 'required_if' => 'To πεδίο :attribute είναι απαραίτητο εκτός αν :other είναι σε :values.', + 'required_with' => 'To πεδίο :attribute είναι απαραίτητο όταν υπάρχουν οι :values.', + 'required_with_all' => 'To πεδίο :attribute είναι απαραίτητο όταν υπάρχουν οι :values.', + 'required_without' => 'To πεδίο :attribute είναι απαραίτητο όταν δεν υπάρχουν οι :values.', + 'required_without_all' => 'To πεδίο :attribute είναι απαραίτητο όταν δεν υπάρχουν καμία από :values.', + 'same' => 'Το πεδίο :attribute και :other πρέπει να είναι ίδια.', + 'safe_url' => 'Ο παρεχόμενος σύνδεσμος μπορεί να μην είναι ασφαλής.', + 'size' => [ + 'numeric' => 'Το :attribute πρέπει να είναι :size.', + 'file' => 'Το :attribute πρέπει να έχει μέγεθος :size kilobytes.', + 'string' => 'Το πεδίο :attribute πρέπει να είναι :size χαρακτήρες.', + 'array' => 'Το πεδίο :attribute πρέπει να περιέχει :size αντικείμενα.', + ], + 'string' => 'Το :attribute πρέπει να είναι συμβολοσειρά.', + 'timezone' => 'Το πεδίο :attribute πρέπει να είναι μία έγκυρη ζώνη ώρας.', + 'totp' => 'Ο παρεχόμενος κωδικός δεν είναι έγκυρος ή έχει λήξει.', + 'unique' => 'Το πεδίο :attribute έχει ήδη χρησιμοποιηθεί.', + 'url' => 'Η μορφή του :attribute δεν είναι έγκυρη.', + 'uploaded' => 'Δεν ήταν δυνατή η αποστολή του αρχείου. Ο διακομιστής ενδέχεται να μην δέχεται αρχεία αυτού του μεγέθους.', + + // Custom validation lines + 'custom' => [ + 'password-confirm' => [ + 'required_with' => 'Απαιτείται επιβεβαίωση κωδικού πρόσβασης', + ], + ], + + // Custom validation attributes + 'attributes' => [], +]; diff --git a/resources/lang/es/editor.php b/resources/lang/es/editor.php index 951090a67..ed77927f4 100644 --- a/resources/lang/es/editor.php +++ b/resources/lang/es/editor.php @@ -66,6 +66,7 @@ return [ 'insert_link_title' => 'Insertar/Editar enlace', 'insert_horizontal_line' => 'Insertar línea horizontal', 'insert_code_block' => 'Insertar bloque de código', + 'edit_code_block' => 'Editar bloque de código', 'insert_drawing' => 'Insertar/editar dibujo', 'drawing_manager' => 'Gestor de dibujo', 'insert_media' => 'Insertar/editar medios', diff --git a/resources/lang/es/entities.php b/resources/lang/es/entities.php index d7f9e5c4f..2887e2cc6 100644 --- a/resources/lang/es/entities.php +++ b/resources/lang/es/entities.php @@ -42,10 +42,14 @@ return [ // 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 personalizados', + 'permissions_desc' => 'Establezca los permisos aquí para anular los permisos por defecto proporcionados por los roles de usuario.', + 'permissions_book_cascade' => 'Los permisos establecidos en los libros se aplicarán a sus capítulos y páginas, a menos que tengan sus propios permisos definidos.', + 'permissions_chapter_cascade' => 'Los permisos establecidos en los capítulos se aplicarán a sus páginas, a menos que tengan sus propios permisos definidos.', 'permissions_save' => 'Guardar permisos', 'permissions_owner' => 'Propietario', + 'permissions_role_everyone_else' => 'Todos los demás', + 'permissions_role_everyone_else_desc' => 'Establecer permisos para todos los roles sin permisos específicos asignados.', + 'permissions_role_override' => 'Reemplazar permisos para el rol', // Search 'search_results' => 'Resultados de búsqueda', diff --git a/resources/lang/es_AR/editor.php b/resources/lang/es_AR/editor.php index 93d974543..269b9e262 100644 --- a/resources/lang/es_AR/editor.php +++ b/resources/lang/es_AR/editor.php @@ -66,6 +66,7 @@ return [ 'insert_link_title' => 'Insertar/Editar Enlace', 'insert_horizontal_line' => 'Insertar línea horizontal', 'insert_code_block' => 'Insertar bloque de código', + 'edit_code_block' => 'Edit code block', 'insert_drawing' => 'Insertar/editar dibujo', 'drawing_manager' => 'Gestor de dibujo', 'insert_media' => 'Insertar/editar media', diff --git a/resources/lang/es_AR/entities.php b/resources/lang/es_AR/entities.php index 9940f3cce..87075ac10 100644 --- a/resources/lang/es_AR/entities.php +++ b/resources/lang/es_AR/entities.php @@ -42,10 +42,14 @@ return [ // 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_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', 'permissions_save' => 'Guardar permisos', 'permissions_owner' => 'Propietario', + 'permissions_role_everyone_else' => 'Everyone Else', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Override permissions for role', // Search 'search_results' => 'Buscar resultados', diff --git a/resources/lang/et/editor.php b/resources/lang/et/editor.php index 631fbf9d3..996cbad90 100644 --- a/resources/lang/et/editor.php +++ b/resources/lang/et/editor.php @@ -66,6 +66,7 @@ return [ 'insert_link_title' => 'Sisesta/muuda linki', 'insert_horizontal_line' => 'Sisesta vahejoon', 'insert_code_block' => 'Sisesta koodiplokk', + 'edit_code_block' => 'Muuda koodiplokki', 'insert_drawing' => 'Sisesta/muuda joonist', 'drawing_manager' => 'Jooniste haldur', 'insert_media' => 'Sisesta/muuda meediat', diff --git a/resources/lang/et/entities.php b/resources/lang/et/entities.php index 2f2f7110a..8168b90f6 100644 --- a/resources/lang/et/entities.php +++ b/resources/lang/et/entities.php @@ -42,10 +42,14 @@ return [ // Permissions and restrictions 'permissions' => 'Õigused', - 'permissions_intro' => 'Kui kohandatud õigused on lubatud, rakendatakse neid eelisjärjekorras, enne rolli õiguseid.', - 'permissions_enable' => 'Luba kohandatud õigused', + 'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', 'permissions_save' => 'Salvesta õigused', 'permissions_owner' => 'Omanik', + 'permissions_role_everyone_else' => 'Everyone Else', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Override permissions for role', // Search 'search_results' => 'Otsingutulemused', diff --git a/resources/lang/eu/editor.php b/resources/lang/eu/editor.php index 2d49d77b0..f951d6292 100644 --- a/resources/lang/eu/editor.php +++ b/resources/lang/eu/editor.php @@ -66,6 +66,7 @@ return [ 'insert_link_title' => 'Txertatu/Aldatu esteka', 'insert_horizontal_line' => 'Txertatu linea horizontala', 'insert_code_block' => 'Txertatu kode-blokea', + 'edit_code_block' => 'Edit code block', 'insert_drawing' => 'Txertatu marrazki berria', 'drawing_manager' => 'Marrazki kudeaketa', 'insert_media' => 'Txertatu/aldatu media', diff --git a/resources/lang/eu/entities.php b/resources/lang/eu/entities.php index 0ec16d947..dc96ca17c 100644 --- a/resources/lang/eu/entities.php +++ b/resources/lang/eu/entities.php @@ -42,10 +42,14 @@ return [ // Permissions and restrictions 'permissions' => 'Baimenak', - 'permissions_intro' => 'Behin hau aktibatuta, baimen hauek lehentasuna izango dute beste edozein rol-engainetik.', - 'permissions_enable' => 'Baimena pertsonalizatuak Gaitu', + 'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', 'permissions_save' => 'Gorde baimenak', 'permissions_owner' => 'Jabea', + 'permissions_role_everyone_else' => 'Everyone Else', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Override permissions for role', // Search 'search_results' => 'Bilaketaren emaitzak', diff --git a/resources/lang/fa/editor.php b/resources/lang/fa/editor.php index fbab475ed..e7071498c 100644 --- a/resources/lang/fa/editor.php +++ b/resources/lang/fa/editor.php @@ -66,6 +66,7 @@ return [ 'insert_link_title' => 'افزودن/ویرایش پیوند', 'insert_horizontal_line' => 'افزودن خط افقی', 'insert_code_block' => 'افزودن بلوک کد', + 'edit_code_block' => 'Edit code block', 'insert_drawing' => 'افزودن/ویرایش طرح', 'drawing_manager' => 'مدیریت طراحی', 'insert_media' => 'افزودن/ویرایش رسانه', diff --git a/resources/lang/fa/entities.php b/resources/lang/fa/entities.php index 8bbb33d67..416e868c5 100644 --- a/resources/lang/fa/entities.php +++ b/resources/lang/fa/entities.php @@ -42,10 +42,14 @@ return [ // Permissions and restrictions 'permissions' => 'مجوزها', - 'permissions_intro' => 'پس از فعال شدن، این مجوزها نسبت به مجوزهای تعیین شده نقش اولویت دارند.', - 'permissions_enable' => 'مجوزهای سفارشی را فعال کنید', + 'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', 'permissions_save' => 'ذخيره مجوزها', 'permissions_owner' => 'مالک', + 'permissions_role_everyone_else' => 'Everyone Else', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Override permissions for role', // Search 'search_results' => 'نتایج جستجو', @@ -136,7 +140,7 @@ return [ 'books_search_this' => 'این کتاب را جستجو کنید', 'books_navigation' => 'ناوبری کتاب', 'books_sort' => 'مرتب سازی مطالب کتاب', - 'books_sort_named' => 'مرتب سازی کتاب:bookName', + 'books_sort_named' => 'مرتب‌سازی کتاب:bookName', 'books_sort_name' => 'مرتب سازی بر اساس نام', 'books_sort_created' => 'مرتب سازی بر اساس تاریخ ایجاد', 'books_sort_updated' => 'مرتب سازی بر اساس تاریخ به روز رسانی', diff --git a/resources/lang/fr/editor.php b/resources/lang/fr/editor.php index 81a9b42fa..4c51f5cfe 100644 --- a/resources/lang/fr/editor.php +++ b/resources/lang/fr/editor.php @@ -66,6 +66,7 @@ return [ 'insert_link_title' => 'Insérer/Modifier un lien', 'insert_horizontal_line' => 'Insérer une ligne horizontale', 'insert_code_block' => 'Insérer un bloc de code', + 'edit_code_block' => 'Edit code block', 'insert_drawing' => 'Insérer/modifier un dessin', 'drawing_manager' => 'Gestionnaire de dessin', 'insert_media' => 'Insérer/modifier un média', diff --git a/resources/lang/fr/entities.php b/resources/lang/fr/entities.php index 4abd2d050..09b71d7ee 100644 --- a/resources/lang/fr/entities.php +++ b/resources/lang/fr/entities.php @@ -42,10 +42,14 @@ return [ // Permissions and restrictions 'permissions' => 'Autorisations', - 'permissions_intro' => 'Une fois activées, ces permissions auront la priorité sur tous les jeux de permissions préexistants.', - 'permissions_enable' => 'Activer les permissions personnalisées', + 'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', 'permissions_save' => 'Enregistrer les permissions', 'permissions_owner' => 'Propriétaire', + 'permissions_role_everyone_else' => 'Tous les autres', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Remplacer les permissions pour le rôle', // Search 'search_results' => 'Résultats de recherche', diff --git a/resources/lang/he/editor.php b/resources/lang/he/editor.php index 659937ecc..6184ec00a 100644 --- a/resources/lang/he/editor.php +++ b/resources/lang/he/editor.php @@ -66,6 +66,7 @@ return [ 'insert_link_title' => 'Insert/Edit Link', 'insert_horizontal_line' => 'Insert horizontal line', 'insert_code_block' => 'Insert code block', + 'edit_code_block' => 'Edit code block', 'insert_drawing' => 'Insert/edit drawing', 'drawing_manager' => 'Drawing manager', 'insert_media' => 'Insert/edit media', diff --git a/resources/lang/he/entities.php b/resources/lang/he/entities.php index 1e073b1e4..399a01027 100644 --- a/resources/lang/he/entities.php +++ b/resources/lang/he/entities.php @@ -42,10 +42,14 @@ return [ // Permissions and restrictions 'permissions' => 'הרשאות', - 'permissions_intro' => 'ברגע שמסומן, הרשאות אלו יגברו על כל הרשאת תפקיד שקיימת', - 'permissions_enable' => 'הפעל הרשאות מותאמות אישית', + 'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', 'permissions_save' => 'שמור הרשאות', 'permissions_owner' => 'בעלים', + 'permissions_role_everyone_else' => 'Everyone Else', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Override permissions for role', // Search 'search_results' => 'תוצאות חיפוש', diff --git a/resources/lang/hr/editor.php b/resources/lang/hr/editor.php index da7ffcf01..faf351da2 100644 --- a/resources/lang/hr/editor.php +++ b/resources/lang/hr/editor.php @@ -66,6 +66,7 @@ return [ 'insert_link_title' => 'Insert/Edit Link', 'insert_horizontal_line' => 'Insert horizontal line', 'insert_code_block' => 'Insert code block', + 'edit_code_block' => 'Edit code block', 'insert_drawing' => 'Insert/edit drawing', 'drawing_manager' => 'Drawing manager', 'insert_media' => 'Insert/edit media', diff --git a/resources/lang/hr/entities.php b/resources/lang/hr/entities.php index db7eb45d3..32718562e 100644 --- a/resources/lang/hr/entities.php +++ b/resources/lang/hr/entities.php @@ -42,10 +42,14 @@ return [ // Permissions and restrictions 'permissions' => 'Dopuštenja', - 'permissions_intro' => 'Jednom postavljene, ove dozvole bit će prioritetne ostalim dopuštenjima.', - 'permissions_enable' => 'Omogući dopuštenje za korištenje', + 'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', 'permissions_save' => 'Spremi dopuštenje', 'permissions_owner' => 'Vlasnik', + 'permissions_role_everyone_else' => 'Everyone Else', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Override permissions for role', // Search 'search_results' => 'Pretraži rezultate', diff --git a/resources/lang/hu/editor.php b/resources/lang/hu/editor.php index d44a83c87..828dea6e1 100644 --- a/resources/lang/hu/editor.php +++ b/resources/lang/hu/editor.php @@ -66,6 +66,7 @@ return [ 'insert_link_title' => 'Hivatkozás Beszúrása/Szerkesztése', 'insert_horizontal_line' => 'Vízszintes vonal beszúrása', 'insert_code_block' => 'Kódrészlet beszúrása', + 'edit_code_block' => 'Edit code block', 'insert_drawing' => 'Rajz beszúrása/szerkesztése', 'drawing_manager' => 'Rajzkezelő', 'insert_media' => 'Media beszúrása/szerkesztése', diff --git a/resources/lang/hu/entities.php b/resources/lang/hu/entities.php index 22e4e54c8..55c1524ed 100644 --- a/resources/lang/hu/entities.php +++ b/resources/lang/hu/entities.php @@ -42,10 +42,14 @@ return [ // Permissions and restrictions 'permissions' => 'Jogosultságok', - 'permissions_intro' => 'Ha engedélyezett, ezek a jogosultságok elsőbbséget élveznek bármely beállított szerepkör jogosultsággal szemben.', - 'permissions_enable' => 'Egyéni jogosultságok engedélyezése', + 'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', 'permissions_save' => 'Jogosultságok mentése', 'permissions_owner' => 'Tulajdonos', + 'permissions_role_everyone_else' => 'Everyone Else', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Override permissions for role', // Search 'search_results' => 'Keresési eredmények', diff --git a/resources/lang/id/editor.php b/resources/lang/id/editor.php index 57a1daa5b..42a7b10c5 100644 --- a/resources/lang/id/editor.php +++ b/resources/lang/id/editor.php @@ -66,6 +66,7 @@ return [ 'insert_link_title' => 'Insert/Edit Link', 'insert_horizontal_line' => 'Insert horizontal line', 'insert_code_block' => 'Insert code block', + 'edit_code_block' => 'Edit code block', 'insert_drawing' => 'Insert/edit drawing', 'drawing_manager' => 'Drawing manager', 'insert_media' => 'Insert/edit media', diff --git a/resources/lang/id/entities.php b/resources/lang/id/entities.php index 9c52ddcd8..db2d7f8b9 100644 --- a/resources/lang/id/entities.php +++ b/resources/lang/id/entities.php @@ -42,10 +42,14 @@ return [ // Permissions and restrictions 'permissions' => 'Izin', - 'permissions_intro' => 'Setelah diaktifkan, izin ini akan menjadi prioritas di atas izin peran yang ditetapkan.', - 'permissions_enable' => 'Aktifkan Izin Kustom', + 'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', 'permissions_save' => 'Simpan Izin', 'permissions_owner' => 'Pemilik', + 'permissions_role_everyone_else' => 'Everyone Else', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Override permissions for role', // Search 'search_results' => 'Hasil Pencarian', diff --git a/resources/lang/it/editor.php b/resources/lang/it/editor.php index 240a540da..39ea9fefb 100644 --- a/resources/lang/it/editor.php +++ b/resources/lang/it/editor.php @@ -66,6 +66,7 @@ return [ 'insert_link_title' => 'Inserisci/Modifica Collegamento', 'insert_horizontal_line' => 'Inserisci Riga Orizzontale', 'insert_code_block' => 'Inserisci blocco di codice', + 'edit_code_block' => 'Modifica blocco di codice', 'insert_drawing' => 'Inserisci/Modifica Disegno', 'drawing_manager' => 'Gestore disegni', 'insert_media' => 'Inserisci/modifica media', diff --git a/resources/lang/it/entities.php b/resources/lang/it/entities.php index 7c31412ad..41b792a7d 100755 --- a/resources/lang/it/entities.php +++ b/resources/lang/it/entities.php @@ -42,10 +42,14 @@ return [ // 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_desc' => 'Imposta qui i permessi per sovrascrivere i permessi predefiniti forniti dai ruoli utente.', + 'permissions_book_cascade' => 'I permessi impostati sui libri si trasmettono automaticamente a cascata ai capitoli e alle pagine figli, a meno che non siano stati definiti permessi propri.', + 'permissions_chapter_cascade' => 'I permessi impostati sui capitoli si trasmettono automaticamente a cascata alle pagine figlie, a meno che non siano stati definiti permessi propri.', 'permissions_save' => 'Salva Permessi', 'permissions_owner' => 'Proprietario', + 'permissions_role_everyone_else' => 'Tutti Gli Altri', + 'permissions_role_everyone_else_desc' => 'Imposta i permessi per tutti i ruoli non specificamente sovrascritti.', + 'permissions_role_override' => 'Sovrascrivere i permessi per il ruolo', // Search 'search_results' => 'Risultati Ricerca', diff --git a/resources/lang/ja/editor.php b/resources/lang/ja/editor.php index 839906e88..0e13bb907 100644 --- a/resources/lang/ja/editor.php +++ b/resources/lang/ja/editor.php @@ -66,6 +66,7 @@ return [ 'insert_link_title' => 'リンクの挿入・編集', 'insert_horizontal_line' => '水平線を挿入', 'insert_code_block' => 'コードブロックを挿入', + 'edit_code_block' => 'Edit code block', 'insert_drawing' => '描画を挿入・編集', 'drawing_manager' => '描画マネージャー', 'insert_media' => 'メディアの挿入・編集', diff --git a/resources/lang/ja/entities.php b/resources/lang/ja/entities.php index fb47fbe7f..ab0774d43 100644 --- a/resources/lang/ja/entities.php +++ b/resources/lang/ja/entities.php @@ -42,10 +42,14 @@ return [ // Permissions and restrictions 'permissions' => '権限', - 'permissions_intro' => 'この設定は各ユーザの役割よりも優先して適用されます。', - 'permissions_enable' => 'カスタム権限設定を有効にする', + 'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', 'permissions_save' => '権限を保存', 'permissions_owner' => '所有者', + 'permissions_role_everyone_else' => 'Everyone Else', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Override permissions for role', // Search 'search_results' => '検索結果', diff --git a/resources/lang/ko/activities.php b/resources/lang/ko/activities.php index debd373af..ab7a46d0f 100644 --- a/resources/lang/ko/activities.php +++ b/resources/lang/ko/activities.php @@ -38,14 +38,14 @@ return [ 'book_sort_notification' => '책 정렬 바꿈', // Bookshelves - 'bookshelf_create' => 'created shelf', - 'bookshelf_create_notification' => 'Shelf successfully created', - 'bookshelf_create_from_book' => 'converted book to shelf', + 'bookshelf_create' => '책꽃이 만들기', + 'bookshelf_create_notification' => '책꽃이 생성 완료', + 'bookshelf_create_from_book' => '책을 책꽃이로 변환', 'bookshelf_create_from_book_notification' => '책을 책꽂이로 변환했습니다.', - 'bookshelf_update' => 'updated shelf', - 'bookshelf_update_notification' => 'Shelf successfully updated', - 'bookshelf_delete' => 'deleted shelf', - 'bookshelf_delete_notification' => 'Shelf successfully deleted', + 'bookshelf_update' => '책꽃이 업데이트', + 'bookshelf_update_notification' => '책꽃이가 업데이트되었습니다', + 'bookshelf_delete' => '선반 삭제', + 'bookshelf_delete_notification' => '책꽃이가 삭제되었습니다.', // Favourites 'favourite_add_notification' => '":name" 북마크에 추가함', diff --git a/resources/lang/ko/editor.php b/resources/lang/ko/editor.php index 11a164d8c..c86f56bb8 100644 --- a/resources/lang/ko/editor.php +++ b/resources/lang/ko/editor.php @@ -41,7 +41,7 @@ return [ 'callout_warning' => '경고', 'callout_danger' => '위험', 'bold' => '굵게', - 'italic' => '기울임', + 'italic' => '기울임체', 'underline' => '밑줄', 'strikethrough' => '취소선', 'superscript' => '윗첨자', @@ -59,58 +59,59 @@ return [ 'list_task' => '작업 목록', 'indent_increase' => '들여쓰기 증가', 'indent_decrease' => '들여쓰기 감소', - 'table' => 'Table', - 'insert_image' => 'Insert image', - 'insert_image_title' => 'Insert/Edit Image', - 'insert_link' => 'Insert/edit link', - 'insert_link_title' => 'Insert/Edit Link', - 'insert_horizontal_line' => 'Insert horizontal line', - 'insert_code_block' => 'Insert code block', - 'insert_drawing' => 'Insert/edit drawing', - 'drawing_manager' => 'Drawing manager', - 'insert_media' => 'Insert/edit media', - 'insert_media_title' => 'Insert/Edit Media', - 'clear_formatting' => 'Clear formatting', - 'source_code' => 'Source code', - 'source_code_title' => 'Source Code', - 'fullscreen' => 'Fullscreen', - 'image_options' => 'Image options', + 'table' => '테이블', + 'insert_image' => '이미지 삽입', + 'insert_image_title' => '이미지 삽입/수정', + 'insert_link' => '링크 삽입/수정', + 'insert_link_title' => '링크 삽입/수정', + 'insert_horizontal_line' => '수평선 삽입', + 'insert_code_block' => '코드 블럭 삽입', + 'edit_code_block' => 'Edit code block', + 'insert_drawing' => '그리기 삽입/수정', + 'drawing_manager' => '그리기 설정', + 'insert_media' => '미디어 삽입/수정', + 'insert_media_title' => '미디어 삽입/수정', + 'clear_formatting' => '양식 초기화', + 'source_code' => '소스 코드', + 'source_code_title' => '소스 코드', + 'fullscreen' => '전체화면', + 'image_options' => '이미지 옵션', // Tables - 'table_properties' => 'Table properties', - 'table_properties_title' => 'Table Properties', - 'delete_table' => 'Delete table', - 'insert_row_before' => 'Insert row before', - 'insert_row_after' => 'Insert row after', - 'delete_row' => 'Delete row', - 'insert_column_before' => 'Insert column before', - 'insert_column_after' => 'Insert column after', - 'delete_column' => 'Delete column', - 'table_cell' => 'Cell', - 'table_row' => 'Row', - 'table_column' => 'Column', - 'cell_properties' => 'Cell properties', - 'cell_properties_title' => 'Cell Properties', - 'cell_type' => 'Cell type', - 'cell_type_cell' => 'Cell', - 'cell_scope' => 'Scope', - 'cell_type_header' => 'Header cell', - 'merge_cells' => 'Merge cells', - 'split_cell' => 'Split cell', - 'table_row_group' => 'Row Group', - 'table_column_group' => 'Column Group', - 'horizontal_align' => 'Horizontal align', - 'vertical_align' => 'Vertical align', - 'border_width' => 'Border width', - 'border_style' => 'Border style', - 'border_color' => 'Border color', - 'row_properties' => 'Row properties', - 'row_properties_title' => 'Row Properties', - 'cut_row' => 'Cut row', - 'copy_row' => 'Copy row', - 'paste_row_before' => 'Paste row before', - 'paste_row_after' => 'Paste row after', - 'row_type' => 'Row type', + 'table_properties' => '테이블 속성', + 'table_properties_title' => '테이블 속성', + 'delete_table' => '테이블 삭제', + 'insert_row_before' => '앞에 행 추가', + 'insert_row_after' => '뒤에 행 추가', + 'delete_row' => '행 삭제', + 'insert_column_before' => '앞에 열 추가', + 'insert_column_after' => '뒤에 열 추가', + 'delete_column' => '열 삭제', + 'table_cell' => '셀', + 'table_row' => '행', + 'table_column' => '열', + 'cell_properties' => '셀 속성', + 'cell_properties_title' => '셀 속성', + 'cell_type' => '셀 스타일', + 'cell_type_cell' => '셀', + 'cell_scope' => '범위', + 'cell_type_header' => '헤더 셀', + 'merge_cells' => '셀 병합', + 'split_cell' => '셀 분할', + 'table_row_group' => '행 그룹', + 'table_column_group' => '열 그룹', + 'horizontal_align' => '가로 맞춤', + 'vertical_align' => '세로 맞춤', + 'border_width' => '테두리 너비', + 'border_style' => '테두리 스타일', + 'border_color' => '테두리 색', + 'row_properties' => '행 속성', + 'row_properties_title' => '열 속성', + 'cut_row' => '행 자르기', + 'copy_row' => '행 복사', + 'paste_row_before' => '앞에 행 붙여넣기', + 'paste_row_after' => '다음 뒤에 행 붙여넣기', + 'row_type' => '행 타입', 'row_type_header' => '머리글', 'row_type_body' => '본문', 'row_type_footer' => '바닥글', @@ -164,8 +165,8 @@ return [ 'link_selector' => 'Link to content', 'shortcuts' => '단축키', 'shortcut' => '단축키', - 'shortcuts_intro' => 'The following shortcuts are available in the editor:', - 'windows_linux' => '(Windows/Linux)', - 'mac' => '(Mac)', - 'description' => 'Description', + 'shortcuts_intro' => '편집기에서 사용할 수 있는 바로 가기는 다음과 같습니다:', + 'windows_linux' => '(윈도우/리눅스)', + 'mac' => '(맥)', + 'description' => '상세정보', ]; diff --git a/resources/lang/ko/entities.php b/resources/lang/ko/entities.php index 48d043c7e..a3bd3f36f 100644 --- a/resources/lang/ko/entities.php +++ b/resources/lang/ko/entities.php @@ -42,10 +42,14 @@ return [ // Permissions and restrictions 'permissions' => '권한', - 'permissions_intro' => '한번 허용하면 이 설정은 사용자 권한에 우선합니다.', - 'permissions_enable' => '설정 허용', + 'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', 'permissions_save' => '권한 저장', 'permissions_owner' => '소유자', + 'permissions_role_everyone_else' => 'Everyone Else', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Override permissions for role', // Search 'search_results' => '검색 결과', diff --git a/resources/lang/lt/editor.php b/resources/lang/lt/editor.php index da7ffcf01..faf351da2 100644 --- a/resources/lang/lt/editor.php +++ b/resources/lang/lt/editor.php @@ -66,6 +66,7 @@ return [ 'insert_link_title' => 'Insert/Edit Link', 'insert_horizontal_line' => 'Insert horizontal line', 'insert_code_block' => 'Insert code block', + 'edit_code_block' => 'Edit code block', 'insert_drawing' => 'Insert/edit drawing', 'drawing_manager' => 'Drawing manager', 'insert_media' => 'Insert/edit media', diff --git a/resources/lang/lt/entities.php b/resources/lang/lt/entities.php index 8cf9fac83..ec89cef9f 100644 --- a/resources/lang/lt/entities.php +++ b/resources/lang/lt/entities.php @@ -42,10 +42,14 @@ return [ // Permissions and restrictions 'permissions' => 'Leidimai', - 'permissions_intro' => 'Įgalinus šias teises, pirmenybė bus teikiama visiems nustatytiems vaidmenų leidimams.', - 'permissions_enable' => 'Įgalinti pasirinktus leidimus', + 'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', 'permissions_save' => 'Išsaugoti leidimus', 'permissions_owner' => 'Savininkas', + 'permissions_role_everyone_else' => 'Everyone Else', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Override permissions for role', // Search 'search_results' => 'Ieškoti rezultatų', diff --git a/resources/lang/lv/editor.php b/resources/lang/lv/editor.php index 1a3d75adb..d2fe32acb 100644 --- a/resources/lang/lv/editor.php +++ b/resources/lang/lv/editor.php @@ -66,6 +66,7 @@ return [ 'insert_link_title' => 'Ievietot/rediģēt saiti', 'insert_horizontal_line' => 'Ievietot horizontālu līniju', 'insert_code_block' => 'Ievietot koda bloku', + 'edit_code_block' => 'Edit code block', 'insert_drawing' => 'Ievietot/rediģēt zīmējumu', 'drawing_manager' => 'Zīmēšanas pārvaldnieks', 'insert_media' => 'Ievietot/rediģēt mediju', diff --git a/resources/lang/lv/entities.php b/resources/lang/lv/entities.php index 48bd922ed..6c87000a0 100644 --- a/resources/lang/lv/entities.php +++ b/resources/lang/lv/entities.php @@ -42,10 +42,14 @@ return [ // Permissions and restrictions 'permissions' => 'Atļaujas', - 'permissions_intro' => 'Kolīdz ieslēgtas, šīs atļaujas ņems prioritāti pār jebkurām citām uzstādītajām atļaujām.', - 'permissions_enable' => 'Ieslēgt pielāgotās atļaujas', + 'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', 'permissions_save' => 'Saglabāt atļaujas', 'permissions_owner' => 'Īpašnieks', + 'permissions_role_everyone_else' => 'Everyone Else', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Override permissions for role', // Search 'search_results' => 'Meklēšanas rezultāti', diff --git a/resources/lang/nb/editor.php b/resources/lang/nb/editor.php index da7ffcf01..faf351da2 100644 --- a/resources/lang/nb/editor.php +++ b/resources/lang/nb/editor.php @@ -66,6 +66,7 @@ return [ 'insert_link_title' => 'Insert/Edit Link', 'insert_horizontal_line' => 'Insert horizontal line', 'insert_code_block' => 'Insert code block', + 'edit_code_block' => 'Edit code block', 'insert_drawing' => 'Insert/edit drawing', 'drawing_manager' => 'Drawing manager', 'insert_media' => 'Insert/edit media', diff --git a/resources/lang/nb/entities.php b/resources/lang/nb/entities.php index 503708352..39ad6d035 100644 --- a/resources/lang/nb/entities.php +++ b/resources/lang/nb/entities.php @@ -42,10 +42,14 @@ return [ // Permissions and restrictions 'permissions' => 'Tilganger', - 'permissions_intro' => 'Når disse er tillatt, vil disse tillatelsene ha prioritet over alle angitte rolletillatelser.', - 'permissions_enable' => 'Aktiver egendefinerte tillatelser', + 'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', 'permissions_save' => 'Lagre tillatelser', 'permissions_owner' => 'Eier', + 'permissions_role_everyone_else' => 'Everyone Else', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Override permissions for role', // Search 'search_results' => 'Søkeresultater', diff --git a/resources/lang/nl/editor.php b/resources/lang/nl/editor.php index 01570e726..55b6c95dd 100644 --- a/resources/lang/nl/editor.php +++ b/resources/lang/nl/editor.php @@ -66,6 +66,7 @@ return [ 'insert_link_title' => 'Link invoegen/bewerken', 'insert_horizontal_line' => 'Horizontale lijn invoegen', 'insert_code_block' => 'Codeblok invoegen', + 'edit_code_block' => 'Edit code block', 'insert_drawing' => 'Tekening invoegen/bewerken', 'drawing_manager' => 'Beheer tekeningen', 'insert_media' => 'Media invoegen/bewerken', diff --git a/resources/lang/nl/entities.php b/resources/lang/nl/entities.php index 3ee393cdb..94aeefe40 100644 --- a/resources/lang/nl/entities.php +++ b/resources/lang/nl/entities.php @@ -42,10 +42,14 @@ return [ // Permissions and restrictions 'permissions' => 'Machtigingen', - 'permissions_intro' => 'Wanneer ingeschakeld, zullen deze machtigingen voorrang krijgen op alle ingestelde rol-machtigingen.', - 'permissions_enable' => 'Aangepaste machtigingen aanzetten', + 'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', 'permissions_save' => 'Machtigingen opslaan', 'permissions_owner' => 'Eigenaar', + 'permissions_role_everyone_else' => 'Everyone Else', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Override permissions for role', // Search 'search_results' => 'Zoekresultaten', diff --git a/resources/lang/nl/settings.php b/resources/lang/nl/settings.php index 32784f527..7df1a3c54 100644 --- a/resources/lang/nl/settings.php +++ b/resources/lang/nl/settings.php @@ -161,7 +161,7 @@ return [ 'roles_system_warning' => 'Wees ervan bewust dat toegang tot een van de bovengenoemde drie machtigingen een gebruiker in staat kan stellen zijn eigen machtigingen of de machtigingen van anderen in het systeem kan wijzigen. Wijs alleen rollen toe met deze machtigingen aan vertrouwde gebruikers.', 'role_asset_desc' => 'Deze machtigingen bepalen de standaard toegang tot de assets binnen het systeem. Machtigingen op boeken, hoofdstukken en pagina\'s overschrijven deze instelling.', 'role_asset_admins' => 'Beheerders krijgen automatisch toegang tot alle inhoud, maar deze opties kunnen gebruikersinterface opties tonen of verbergen.', - 'role_asset_image_view_note' => 'This relates to visibility within the image manager. Actual access of uploaded image files will be dependant upon system image storage option.', + 'role_asset_image_view_note' => 'Dit heeft betrekking op de zichtbaarheid binnen de afbeeldingsbeheerder. De werkelijke toegang tot geüploade afbeeldingsbestanden hangt af van de gekozen opslagmethode.', 'role_all' => 'Alles', 'role_own' => 'Eigen', 'role_controlled_by_asset' => 'Gecontroleerd door de asset waar deze is geüpload', diff --git a/resources/lang/pl/activities.php b/resources/lang/pl/activities.php index 77a8337dd..880dc219b 100644 --- a/resources/lang/pl/activities.php +++ b/resources/lang/pl/activities.php @@ -38,14 +38,14 @@ return [ 'book_sort_notification' => 'Książka posortowana pomyślnie', // Bookshelves - 'bookshelf_create' => 'created shelf', - 'bookshelf_create_notification' => 'Shelf successfully created', - 'bookshelf_create_from_book' => 'converted book to shelf', + 'bookshelf_create' => 'utworzył półkę', + 'bookshelf_create_notification' => 'Półka utworzona pomyślnie', + 'bookshelf_create_from_book' => 'skonwertował książkę na półkę', 'bookshelf_create_from_book_notification' => 'Książka została pomyślnie skonwertowana na półkę', - 'bookshelf_update' => 'updated shelf', - 'bookshelf_update_notification' => 'Shelf successfully updated', - 'bookshelf_delete' => 'deleted shelf', - 'bookshelf_delete_notification' => 'Shelf successfully deleted', + 'bookshelf_update' => 'zaktualizował półkę', + 'bookshelf_update_notification' => 'Półka zaktualizowana pomyślnie', + 'bookshelf_delete' => 'usunął półkę', + 'bookshelf_delete_notification' => 'Półka usunięta pomyślnie', // Favourites 'favourite_add_notification' => '":name" został dodany do Twoich ulubionych', diff --git a/resources/lang/pl/editor.php b/resources/lang/pl/editor.php index ea26c636c..ceae014ff 100644 --- a/resources/lang/pl/editor.php +++ b/resources/lang/pl/editor.php @@ -66,6 +66,7 @@ return [ 'insert_link_title' => 'Wstaw/Edytuj Link', 'insert_horizontal_line' => 'Wstaw linię poziomą', 'insert_code_block' => 'Wstaw blok kodu', + 'edit_code_block' => 'Edit code block', 'insert_drawing' => 'Wstaw/Edytuj rysunek', 'drawing_manager' => 'Menedżer rysunków', 'insert_media' => 'Wstaw/edytuj multimedia', diff --git a/resources/lang/pl/entities.php b/resources/lang/pl/entities.php index d099a2972..372ae34bf 100644 --- a/resources/lang/pl/entities.php +++ b/resources/lang/pl/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Zaktualizowano :timeLength', 'meta_updated_name' => 'Zaktualizowano :timeLength przez :user', 'meta_owned_name' => 'Właściciel: :user', - 'meta_reference_page_count' => 'Referenced on 1 page|Referenced on :count pages', + 'meta_reference_page_count' => 'Odniesienie na 1 stronie|Odniesienie na :count stronach', 'entity_select' => 'Wybór obiektu', 'entity_select_lack_permission' => 'Nie masz wymaganych uprawnień do wybrania tej pozycji', 'images' => 'Obrazki', @@ -42,10 +42,14 @@ return [ // 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_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', 'permissions_save' => 'Zapisz uprawnienia', 'permissions_owner' => 'Właściciel', + 'permissions_role_everyone_else' => 'Everyone Else', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Override permissions for role', // Search 'search_results' => 'Wyniki wyszukiwania', @@ -91,20 +95,20 @@ return [ 'shelves_drag_books' => 'Przeciągnij książki poniżej, aby dodać je do tej półki', 'shelves_empty_contents' => 'Ta półka nie ma przypisanych żadnych książek', 'shelves_edit_and_assign' => 'Edytuj półkę aby przypisać książki', - 'shelves_edit_named' => 'Edit Shelf :name', - 'shelves_edit' => 'Edit Shelf', - 'shelves_delete' => 'Delete Shelf', - 'shelves_delete_named' => 'Delete Shelf :name', - 'shelves_delete_explain' => "This will delete the shelf with the name ':name'. Contained books will not be deleted.", - 'shelves_delete_confirmation' => 'Are you sure you want to delete this shelf?', - 'shelves_permissions' => 'Shelf Permissions', - 'shelves_permissions_updated' => 'Shelf Permissions Updated', - 'shelves_permissions_active' => 'Shelf Permissions Active', - 'shelves_permissions_cascade_warning' => 'Permissions on shelves do not automatically cascade to contained books. This is because a book can exist on multiple shelves. Permissions can however be copied down to child books using the option found below.', + 'shelves_edit_named' => 'Edytuj półkę :name', + 'shelves_edit' => 'Edytuj półkę', + 'shelves_delete' => 'Usuń półkę', + 'shelves_delete_named' => 'Usuń półkę :name', + 'shelves_delete_explain' => "Ta operacja usunie półkę o nazwie ':name'. Książki z tej półki nie zostaną usunięte.", + 'shelves_delete_confirmation' => 'Czy jesteś pewien, że chcesz usunąć tę półkę?', + 'shelves_permissions' => 'Uprawnienia półki', + 'shelves_permissions_updated' => 'Uprawnienia półki zostały zaktualizowane', + 'shelves_permissions_active' => 'Uprawnienia półki są aktywne', + 'shelves_permissions_cascade_warning' => 'Uprawnienia na półkach nie są automatycznie nakładane na zawartych w nich książkach. Dzieje się tak dlatego, że książka może istnieć na wielu półkach. Uprawnienia można jednak skopiować do książek podrzędnych, korzystając z opcji znajdującej się poniżej.', 'shelves_copy_permissions_to_books' => 'Skopiuj uprawnienia do książek', 'shelves_copy_permissions' => 'Skopiuj uprawnienia', - 'shelves_copy_permissions_explain' => 'This will apply the current permission settings of this shelf to all books contained within. Before activating, ensure any changes to the permissions of this shelf have been saved.', - 'shelves_copy_permission_success' => 'Shelf permissions copied to :count books', + 'shelves_copy_permissions_explain' => 'To spowoduje zastosowanie obecnych ustawień uprawnień tej półki na wszystkich książkach w niej zawartych. Przed aktywacją upewnij się, że wszelkie zmiany w uprawnieniach tej półki zostały zapisane.', + 'shelves_copy_permission_success' => 'Uprawnienia półki zostały skopiowane do :count książek', // Books 'book' => 'Książka', @@ -248,7 +252,7 @@ return [ 'pages_edit_content_link' => 'Edytuj zawartość', 'pages_permissions_active' => 'Uprawnienia strony są aktywne', 'pages_initial_revision' => 'Pierwsze wydanie', - 'pages_references_update_revision' => 'System auto-update of internal links', + 'pages_references_update_revision' => 'Automatyczna aktualizacja wewnętrznych linków', 'pages_initial_name' => 'Nowa strona', 'pages_editing_draft_notification' => 'Edytujesz obecnie wersję roboczą, która była ostatnio zapisana :timeDiff.', 'pages_draft_edited_notification' => 'Od tego czasu ta strona była zmieniana. Zalecane jest odrzucenie tej wersji roboczej.', @@ -372,7 +376,7 @@ return [ 'convert_chapter_confirm' => 'Czy na pewno chcesz skonwertować ten rozdział?', // References - 'references' => 'References', - 'references_none' => 'There are no tracked references to this item.', - 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + 'references' => 'Odniesienia', + 'references_none' => 'Brak śledzonych odwołań do tego elementu.', + 'references_to_desc' => 'Poniżej znajdują się wszystkie znane strony w systemie, które odnoszą się do tego elementu.', ]; diff --git a/resources/lang/pl/errors.php b/resources/lang/pl/errors.php index b80a5f223..e493705b5 100644 --- a/resources/lang/pl/errors.php +++ b/resources/lang/pl/errors.php @@ -58,7 +58,7 @@ return [ // Entities 'entity_not_found' => 'Nie znaleziono obiektu', - 'bookshelf_not_found' => 'Shelf not found', + 'bookshelf_not_found' => 'Nie znaleziono półki', 'book_not_found' => 'Nie znaleziono książki', 'page_not_found' => 'Nie znaleziono strony', 'chapter_not_found' => 'Nie znaleziono rozdziału', diff --git a/resources/lang/pl/settings.php b/resources/lang/pl/settings.php index fd3d89bfc..99b13bcc7 100644 --- a/resources/lang/pl/settings.php +++ b/resources/lang/pl/settings.php @@ -81,7 +81,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!', 'maint_send_test_email' => 'Wyślij testową wiadomość e-mail', - 'maint_send_test_email_desc' => 'Ta opcje wyśle wiadomość testową na adres e-mail podany w Twoim profilu', + 'maint_send_test_email_desc' => 'Ta opcja wyśle wiadomość testową na adres e-mail podany w Twoim profilu.', 'maint_send_test_email_run' => 'Wyślij testową wiadomość e-mail', 'maint_send_test_email_success' => 'E-mail wysłany na adres :address', 'maint_send_test_email_mail_subject' => 'E-mail testowy', @@ -89,10 +89,10 @@ return [ 'maint_send_test_email_mail_text' => 'Gratulacje! Otrzymałeś tego e-maila więc Twoje ustawienia poczty elektronicznej wydają się być prawidłowo skonfigurowane.', 'maint_recycle_bin_desc' => 'Usunięte półki, książki, rozdziały i strony są wysyłane do kosza, aby mogły zostać przywrócone lub trwale usunięte. Starsze przedmioty w koszu mogą zostać automatycznie usunięte po pewnym czasie w zależności od konfiguracji systemu.', 'maint_recycle_bin_open' => 'Otwórz kosz', - 'maint_regen_references' => 'Regenerate References', - 'maint_regen_references_desc' => 'This action will rebuild the cross-item reference index within the database. This is usually handled automatically but this action can be useful to index old content or content added via unofficial methods.', - 'maint_regen_references_success' => 'Reference index has been regenerated!', - 'maint_timeout_command_note' => 'Note: This action can take time to run, which can lead to timeout issues in some web environments. As an alternative, this action be performed using a terminal command.', + 'maint_regen_references' => 'Zregeneruj odniesienia', + 'maint_regen_references_desc' => 'Ta akcja przebuduje bazodanowy indeks referencji między pozycjami. Zazwyczaj jest to obsługiwane automatycznie, jednak ta akcja wciąż może być przydatna do indeksowania starej zawartości, lub dodanej nieoficjalnymi metodami.', + 'maint_regen_references_success' => 'Indeks referencji został zregenerowany!', + 'maint_timeout_command_note' => 'Uwaga: Ta akcja potrzebuje czasu na wykonanie, co może prowadzić do problemów z limitami czasu utrzymywania połączenia w niektórych środowiskach webowych. Alternatywnie ta akcja może być wykonana z użyciem polecenia terminalowego.', // Recycle Bin 'recycle_bin' => 'Kosz', @@ -161,7 +161,7 @@ return [ 'roles_system_warning' => 'Pamiętaj, że dostęp do trzech powyższych uprawnień może pozwolić użytkownikowi na zmianę własnych uprawnień lub uprawnień innych osób w systemie. Przypisz tylko role z tymi uprawnieniami do zaufanych użytkowników.', 'role_asset_desc' => 'Te ustawienia kontrolują zarządzanie zasobami systemu. Uprawnienia książek, rozdziałów i stron nadpisują te ustawienia.', 'role_asset_admins' => 'Administratorzy mają automatycznie dostęp do wszystkich treści, ale te opcję mogą być pokazywać lub ukrywać opcje interfejsu użytkownika.', - 'role_asset_image_view_note' => 'This relates to visibility within the image manager. Actual access of uploaded image files will be dependant upon system image storage option.', + 'role_asset_image_view_note' => 'To odnosi się do widoczności w ramach menedżera obrazów. Rzeczywista możliwość dostępu do przesłanych plików obrazów będzie zależeć od systemowej opcji przechowywania obrazów.', 'role_all' => 'Wszyscy', 'role_own' => 'Własne', 'role_controlled_by_asset' => 'Kontrolowane przez zasób, do którego zostały udostępnione', diff --git a/resources/lang/pt/editor.php b/resources/lang/pt/editor.php index 51e99e8e3..2bf383e0d 100644 --- a/resources/lang/pt/editor.php +++ b/resources/lang/pt/editor.php @@ -66,6 +66,7 @@ return [ 'insert_link_title' => 'Inserir/Editar link', 'insert_horizontal_line' => 'Inserir linha horizontal', 'insert_code_block' => 'Inserir código fonte', + 'edit_code_block' => 'Edit code block', 'insert_drawing' => 'Inserir/editar desenho', 'drawing_manager' => 'Gestor de desenho', 'insert_media' => 'Inserir/editar mídia', diff --git a/resources/lang/pt/entities.php b/resources/lang/pt/entities.php index 37399d78f..bc4ef6ba8 100644 --- a/resources/lang/pt/entities.php +++ b/resources/lang/pt/entities.php @@ -42,10 +42,14 @@ return [ // Permissions and restrictions 'permissions' => 'Permissões', - 'permissions_intro' => 'Uma vez ativadas, estas permissões terão prioridade sobre quaisquer outro conjunto de permissões.', - 'permissions_enable' => 'Ativar Permissões Personalizadas', + 'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', 'permissions_save' => 'Guardar Permissões', 'permissions_owner' => 'Proprietário', + 'permissions_role_everyone_else' => 'Everyone Else', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Override permissions for role', // Search 'search_results' => 'Resultado(s) da Pesquisa', diff --git a/resources/lang/pt_BR/activities.php b/resources/lang/pt_BR/activities.php index 427648312..3debcc4e6 100644 --- a/resources/lang/pt_BR/activities.php +++ b/resources/lang/pt_BR/activities.php @@ -6,7 +6,7 @@ return [ // Pages - 'page_create' => 'criou a página', + 'page_create' => 'página criada', 'page_create_notification' => 'Página criada com sucesso', 'page_update' => 'atualizou a página', 'page_update_notification' => 'Página atualizada com sucesso', @@ -38,14 +38,14 @@ return [ 'book_sort_notification' => 'Livro reordenado com sucesso', // Bookshelves - 'bookshelf_create' => 'created shelf', - 'bookshelf_create_notification' => 'Shelf successfully created', - 'bookshelf_create_from_book' => 'converted book to shelf', + 'bookshelf_create' => 'prateleira criada', + 'bookshelf_create_notification' => 'Prateleira criada com sucesso', + 'bookshelf_create_from_book' => 'livro convertido em estante', 'bookshelf_create_from_book_notification' => 'Capítulo convertido com sucesso em um livro', - 'bookshelf_update' => 'updated shelf', - 'bookshelf_update_notification' => 'Shelf successfully updated', - 'bookshelf_delete' => 'deleted shelf', - 'bookshelf_delete_notification' => 'Shelf successfully deleted', + 'bookshelf_update' => 'prateleira atualizada', + 'bookshelf_update_notification' => 'Prateleira atualizada com sucesso', + 'bookshelf_delete' => 'prateleira excluída', + 'bookshelf_delete_notification' => 'Prateleira excluída com sucesso', // Favourites 'favourite_add_notification' => '":name" foi adicionada aos seus favoritos', diff --git a/resources/lang/pt_BR/auth.php b/resources/lang/pt_BR/auth.php index 17ebcb16e..60812af0f 100644 --- a/resources/lang/pt_BR/auth.php +++ b/resources/lang/pt_BR/auth.php @@ -39,9 +39,9 @@ return [ 'register_success' => 'Obrigado por se cadastrar! Você agora encontra-se cadastrado(a) e logado(a).', // Login auto-initiation - 'auto_init_starting' => 'Attempting Login', - 'auto_init_starting_desc' => 'We\'re contacting your authentication system to start the login process. If there\'s no progress after 5 seconds you can try clicking the link below.', - 'auto_init_start_link' => 'Proceed with authentication', + 'auto_init_starting' => 'Tentando fazer login', + 'auto_init_starting_desc' => 'Estamos entrando em contato com seu sistema de autenticação para iniciar o processo de login. Se não houver progresso após 5 segundos, você pode tentar clicar no link abaixo.', + 'auto_init_start_link' => 'Prossiga com a autenticação', // Password Reset 'reset_password' => 'Redefinir Senha', diff --git a/resources/lang/pt_BR/editor.php b/resources/lang/pt_BR/editor.php index aaf233a6f..c0f0d406b 100644 --- a/resources/lang/pt_BR/editor.php +++ b/resources/lang/pt_BR/editor.php @@ -66,6 +66,7 @@ return [ 'insert_link_title' => 'Inserir/Editar link', 'insert_horizontal_line' => 'Insert horizontal line', 'insert_code_block' => 'Inserir o bloco de endereço', + 'edit_code_block' => 'Editar bloco de código', 'insert_drawing' => 'Inserir/editar desenho', 'drawing_manager' => 'Gerente de desenho', 'insert_media' => 'Inserir/editar mídia', @@ -157,7 +158,7 @@ return [ 'about' => 'Sobre o editor', 'about_title' => 'Sobre o Editor WYSIWYG', 'editor_license' => 'Licença do Editor e Direitos Autorais', - 'editor_tiny_license' => 'This editor is built using :tinyLink which is provided under the MIT license.', + 'editor_tiny_license' => 'Este editor é construído usando :tinyLink que é fornecido sob a licença MIT.', 'editor_tiny_license_link' => 'Os dados relativos aos direitos de autor e à licença do TinyMCE podem ser encontrados aqui.', 'save_continue' => 'Salvar e continuar', 'callouts_cycle' => '(Continue pressionando para alternar através de tipos)', diff --git a/resources/lang/pt_BR/entities.php b/resources/lang/pt_BR/entities.php index fbede6da7..68f95c420 100644 --- a/resources/lang/pt_BR/entities.php +++ b/resources/lang/pt_BR/entities.php @@ -23,9 +23,9 @@ return [ 'meta_updated' => 'Atualizado :timeLength', 'meta_updated_name' => 'Atualizado :timeLength por :user', 'meta_owned_name' => 'De :user', - 'meta_reference_page_count' => 'Referenced on 1 page|Referenced on :count pages', + 'meta_reference_page_count' => 'Referenciado em 1 página|Referenciado em :count pages', 'entity_select' => 'Seleção de Entidade', - 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', + 'entity_select_lack_permission' => 'Você não tem as permissões necessárias para selecionar este item', 'images' => 'Imagens', 'my_recent_drafts' => 'Meus Rascunhos Recentes', 'my_recently_viewed' => 'Visualizados por mim Recentemente', @@ -42,10 +42,14 @@ return [ // Permissions and restrictions 'permissions' => 'Permissões', - 'permissions_intro' => 'Uma vez habilitadas, estas permissões terão prioridade sobre outro conjunto de permissões.', - 'permissions_enable' => 'Habilitar Permissões Customizadas', + 'permissions_desc' => 'Defina as permissões aqui para substituir as permissões padrão fornecidas pelas funções do usuário.', + 'permissions_book_cascade' => 'As permissões definidas em livros serão automaticamente em cascata para capítulos e páginas filho, a menos que tenham suas próprias permissões definidas.', + 'permissions_chapter_cascade' => 'As permissões definidas nos capítulos serão automaticamente em cascata para as páginas filhas, a menos que tenham suas próprias permissões definidas.', 'permissions_save' => 'Salvar Permissões', 'permissions_owner' => 'Proprietário', + 'permissions_role_everyone_else' => 'Todos os outros', + 'permissions_role_everyone_else_desc' => 'Defina permissões para todas as funções não especificamente substituídas.', + 'permissions_role_override' => 'Substituir permissões para função', // Search 'search_results' => 'Resultado(s) da Pesquisa', @@ -88,23 +92,23 @@ return [ 'shelves_save' => 'Salvar Prateleira', 'shelves_books' => 'Livros nesta prateleira', 'shelves_add_books' => 'Adicionar livros a esta prateleira', - 'shelves_drag_books' => 'Drag books below to add them to this shelf', + 'shelves_drag_books' => 'Arraste os livros abaixo para adicioná-los a esta estante', 'shelves_empty_contents' => 'Esta prateleira não possui livros atribuídos a ela', 'shelves_edit_and_assign' => 'Editar prateleira para atribuir livros', - 'shelves_edit_named' => 'Edit Shelf :name', - 'shelves_edit' => 'Edit Shelf', - 'shelves_delete' => 'Delete Shelf', - 'shelves_delete_named' => 'Delete Shelf :name', - 'shelves_delete_explain' => "This will delete the shelf with the name ':name'. Contained books will not be deleted.", - 'shelves_delete_confirmation' => 'Are you sure you want to delete this shelf?', - 'shelves_permissions' => 'Shelf Permissions', - 'shelves_permissions_updated' => 'Shelf Permissions Updated', - 'shelves_permissions_active' => 'Shelf Permissions Active', - 'shelves_permissions_cascade_warning' => 'Permissions on shelves do not automatically cascade to contained books. This is because a book can exist on multiple shelves. Permissions can however be copied down to child books using the option found below.', + 'shelves_edit_named' => 'Editar estante :name', + 'shelves_edit' => 'Editar prateleira', + 'shelves_delete' => 'Excluir prateleira', + 'shelves_delete_named' => 'Excluir estante :name', + 'shelves_delete_explain' => "Isso excluirá a estante com o nome ':name'. Os livros contidos não serão excluídos.", + 'shelves_delete_confirmation' => 'Tem certeza de que deseja excluir esta prateleira?', + 'shelves_permissions' => 'Permissões de prateleira', + 'shelves_permissions_updated' => 'Permissões de prateleira atualizadas', + 'shelves_permissions_active' => 'Permissões de prateleira ativas', + 'shelves_permissions_cascade_warning' => 'As permissões nas prateleiras não são automaticamente em cascata para os livros contidos. Isso ocorre porque um livro pode existir em várias prateleiras. No entanto, as permissões podem ser copiadas para livros filhos usando a opção encontrada abaixo.', 'shelves_copy_permissions_to_books' => 'Copiar Permissões para Livros', 'shelves_copy_permissions' => 'Copiar Permissões', - 'shelves_copy_permissions_explain' => 'This will apply the current permission settings of this shelf to all books contained within. Before activating, ensure any changes to the permissions of this shelf have been saved.', - 'shelves_copy_permission_success' => 'Shelf permissions copied to :count books', + 'shelves_copy_permissions_explain' => 'Isso aplicará as configurações de permissão atuais desta estante a todos os livros contidos nela. Antes de ativar, verifique se todas as alterações nas permissões desta prateleira foram salvas.', + 'shelves_copy_permission_success' => 'Permissões de prateleira copiadas para :count books', // Books 'book' => 'Livro', @@ -171,7 +175,7 @@ return [ 'chapters_permissions_active' => 'Permissões de Capítulo Ativas', 'chapters_permissions_success' => 'Permissões de Capítulo Atualizadas', 'chapters_search_this' => 'Pesquisar neste Capítulo', - 'chapter_sort_book' => 'Sort Book', + 'chapter_sort_book' => 'Classificar livro', // Pages 'page' => 'Página', @@ -248,7 +252,7 @@ return [ 'pages_edit_content_link' => 'Editar Conteúdo', 'pages_permissions_active' => 'Permissões de Página Ativas', 'pages_initial_revision' => 'Publicação Inicial', - 'pages_references_update_revision' => 'System auto-update of internal links', + 'pages_references_update_revision' => 'Atualização automática do sistema de links internos', 'pages_initial_name' => 'Nova Página', 'pages_editing_draft_notification' => 'Você está atualmente editando um rascunho que foi salvo da última vez em :timeDiff.', 'pages_draft_edited_notification' => 'Essa página foi atualizada desde então. É recomendado que você descarte esse rascunho.', @@ -360,19 +364,19 @@ return [ 'copy_consider_access' => 'Uma alteração de localização, proprietário ou permissões pode resultar em que este conteúdo seja acessível para aqueles previamente sem acesso.', // Conversions - 'convert_to_shelf' => 'Convert to Shelf', - 'convert_to_shelf_contents_desc' => 'You can convert this book to a new shelf with the same contents. Chapters contained within this book will be converted to new books. If this book contains any pages, that are not in a chapter, this book will be renamed and contain such pages, and this book will become part of the new shelf.', - 'convert_to_shelf_permissions_desc' => 'Any permissions set on this book will be copied to the new shelf and to all new child books that don\'t have their own permissions enforced. Note that permissions on shelves do not auto-cascade to content within, as they do for books.', - 'convert_book' => 'Convert Book', - 'convert_book_confirm' => 'Are you sure you want to convert this book?', - 'convert_undo_warning' => 'This cannot be as easily undone.', - 'convert_to_book' => 'Convert to Book', - 'convert_to_book_desc' => 'You can convert this chapter to a new book with the same contents. Any permissions set on this chapter will be copied to the new book but any inherited permissions, from the parent book, will not be copied which could lead to a change of access control.', - 'convert_chapter' => 'Convert Chapter', - 'convert_chapter_confirm' => 'Are you sure you want to convert this chapter?', + 'convert_to_shelf' => 'Converter para estante', + 'convert_to_shelf_contents_desc' => 'Você pode converter este livro em uma nova estante com o mesmo conteúdo. Os capítulos contidos neste livro serão convertidos em novos livros. Se este livro contiver quaisquer páginas que não estejam em um capítulo, este livro será renomeado e conterá tais páginas, e este livro se tornará parte da nova estante.', + 'convert_to_shelf_permissions_desc' => 'Todas as permissões definidas neste livro serão copiadas para a nova estante e para todos os novos livros filhos que não tiverem suas próprias permissões aplicadas. Observe que as permissões nas estantes não se propagam automaticamente para o conteúdo, como acontece com os livros.', + 'convert_book' => 'Converter Livro', + 'convert_book_confirm' => 'Tem certeza de que deseja converter este livro?', + 'convert_undo_warning' => 'Isso não pode ser desfeito tão facilmente.', + 'convert_to_book' => 'Converter em livro', + 'convert_to_book_desc' => 'Você pode converter este capítulo em um novo livro com o mesmo conteúdo. Quaisquer permissões definidas neste capítulo serão copiadas para o novo livro, mas quaisquer permissões herdadas, do livro pai, não serão copiadas, o que pode levar a uma alteração do controle de acesso.', + 'convert_chapter' => 'Converter capítulo', + 'convert_chapter_confirm' => 'Tem certeza de que deseja converter este capítulo?', // References - 'references' => 'References', - 'references_none' => 'There are no tracked references to this item.', - 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + 'references' => 'Referências', + 'references_none' => 'Não há referências rastreadas para este item.', + 'references_to_desc' => 'Abaixo estão todas as páginas conhecidas no sistema que vinculam a este item.', ]; diff --git a/resources/lang/pt_BR/errors.php b/resources/lang/pt_BR/errors.php index afa0b3962..54b6c9fd5 100644 --- a/resources/lang/pt_BR/errors.php +++ b/resources/lang/pt_BR/errors.php @@ -58,7 +58,7 @@ return [ // Entities 'entity_not_found' => 'Entidade não encontrada', - 'bookshelf_not_found' => 'Shelf not found', + 'bookshelf_not_found' => 'Prateleira não encontrada', 'book_not_found' => 'Livro não encontrado', 'page_not_found' => 'Página não encontrada', 'chapter_not_found' => 'Capítulo não encontrado', diff --git a/resources/lang/pt_BR/settings.php b/resources/lang/pt_BR/settings.php index 5d5a5f61d..5f6294846 100644 --- a/resources/lang/pt_BR/settings.php +++ b/resources/lang/pt_BR/settings.php @@ -89,10 +89,10 @@ return [ 'maint_send_test_email_mail_text' => 'Parabéns! Já que você recebeu esta notificação, suas opções de e-mail parecem estar configuradas corretamente.', 'maint_recycle_bin_desc' => 'Prateleiras, livros, capítulos e páginas deletados são mandados para a lixeira podendo assim ser restaurados ou excluídos permanentemente. Itens mais antigos da lixeira podem vir a ser automaticamente removidos da lixeira após um tempo dependendo da configuração do sistema.', 'maint_recycle_bin_open' => 'Abrir Lixeira', - 'maint_regen_references' => 'Regenerate References', - 'maint_regen_references_desc' => 'This action will rebuild the cross-item reference index within the database. This is usually handled automatically but this action can be useful to index old content or content added via unofficial methods.', - 'maint_regen_references_success' => 'Reference index has been regenerated!', - 'maint_timeout_command_note' => 'Note: This action can take time to run, which can lead to timeout issues in some web environments. As an alternative, this action be performed using a terminal command.', + 'maint_regen_references' => 'Regenerar referências', + 'maint_regen_references_desc' => 'Essa ação reconstruirá o índice de referência entre itens no banco de dados. Isso geralmente é tratado automaticamente, mas essa ação pode ser útil para indexar conteúdo antigo ou adicionado por métodos não oficiais.', + 'maint_regen_references_success' => 'O índice de referência foi regenerado!', + 'maint_timeout_command_note' => 'Observação: essa ação pode levar algum tempo para ser executada, o que pode levar a problemas de tempo limite em alguns ambientes da Web. Como alternativa, esta ação pode ser executada usando um comando de terminal.', // Recycle Bin 'recycle_bin' => 'Lixeira', @@ -161,7 +161,7 @@ return [ 'roles_system_warning' => 'Esteja ciente de que o acesso a qualquer uma das três permissões acima pode permitir que um usuário altere seus próprios privilégios ou privilégios de outros usuários no sistema. Apenas atribua cargos com essas permissões para usuários confiáveis.', '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.', 'role_asset_admins' => 'Administradores recebem automaticamente acesso a todo o conteúdo, mas essas opções podem mostrar ou ocultar as opções da Interface de Usuário.', - 'role_asset_image_view_note' => 'This relates to visibility within the image manager. Actual access of uploaded image files will be dependant upon system image storage option.', + 'role_asset_image_view_note' => 'Isso está relacionado à visibilidade no gerenciador de imagens. O acesso real dos arquivos de imagem carregados dependerá da opção de armazenamento de imagem do sistema.', 'role_all' => 'Todos', 'role_own' => 'Próprio', 'role_controlled_by_asset' => 'Controlado pelos ativos nos quais o upload foi realizado', @@ -215,7 +215,7 @@ return [ 'users_api_tokens_docs' => 'Documentação da API', 'users_mfa' => 'Autenticação de Múltiplos Fatores', 'users_mfa_desc' => 'A autenticação multi-fator adiciona outra camada de segurança à sua conta.', - 'users_mfa_x_methods' => ':count método configurado):count métodos configurados', + 'users_mfa_x_methods' => ':count método configurado|:count métodos configurados', 'users_mfa_configure' => 'Configurar Métodos', // API Tokens diff --git a/resources/lang/ro/editor.php b/resources/lang/ro/editor.php index 384d93212..eef26658f 100644 --- a/resources/lang/ro/editor.php +++ b/resources/lang/ro/editor.php @@ -66,6 +66,7 @@ return [ 'insert_link_title' => 'Inserare/Editare link', 'insert_horizontal_line' => 'Inserează linie orizontală', 'insert_code_block' => 'Inserează bloc de cod', + 'edit_code_block' => 'Edit code block', 'insert_drawing' => 'Inserare/editare desen', 'drawing_manager' => 'Manager de desene', 'insert_media' => 'Inserare/editare media', diff --git a/resources/lang/ro/entities.php b/resources/lang/ro/entities.php index 5822c3233..fec396602 100644 --- a/resources/lang/ro/entities.php +++ b/resources/lang/ro/entities.php @@ -42,10 +42,14 @@ return [ // Permissions and restrictions 'permissions' => 'Permisiuni', - 'permissions_intro' => 'Odată activat, aceste permisiuni vor avea prioritate față de toate permisiunile de rol stabilite.', - 'permissions_enable' => 'Activare permisiuni personalizate', + 'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', 'permissions_save' => 'Salvează permisiuni', 'permissions_owner' => 'Proprietar', + 'permissions_role_everyone_else' => 'Everyone Else', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Override permissions for role', // Search 'search_results' => 'Rezultatele căutării', diff --git a/resources/lang/ro/settings.php b/resources/lang/ro/settings.php index fd11ed99d..b9e104c41 100644 --- a/resources/lang/ro/settings.php +++ b/resources/lang/ro/settings.php @@ -89,10 +89,10 @@ return [ 'maint_send_test_email_mail_text' => 'Felicitări! Deoarece ai primit această notificare prin e-mail, setările de e-mail par să fie configurate corespunzător.', 'maint_recycle_bin_desc' => 'Rafturile, cărțile, capitole și paginile șterse se trimit la coșul de gunoi pentru a putea fi restaurate sau șterse definitiv. Elementele mai vechi din coșul de gunoi pot fi eliminate automat după o vreme, în funcție de configurația sistemului.', 'maint_recycle_bin_open' => 'Deschide coșul de gunoi', - 'maint_regen_references' => 'Regenerate References', - 'maint_regen_references_desc' => 'This action will rebuild the cross-item reference index within the database. This is usually handled automatically but this action can be useful to index old content or content added via unofficial methods.', - 'maint_regen_references_success' => 'Reference index has been regenerated!', - 'maint_timeout_command_note' => 'Note: This action can take time to run, which can lead to timeout issues in some web environments. As an alternative, this action be performed using a terminal command.', + 'maint_regen_references' => 'Regenerează referințe', + 'maint_regen_references_desc' => 'Această acțiune va reconstrui indexul de referință al elementului încrucișat în baza de date. Acest lucru este de obicei manipulat automat, dar această acțiune poate fi utilă pentru a indexa conținutul vechi sau conținutul adăugat prin metode neoficiale.', + 'maint_regen_references_success' => 'Indicele de referință a fost regenerat!', + 'maint_timeout_command_note' => 'Notă: Această acțiune necesită timp pentru a funcționa, ceea ce poate duce la apariția unor probleme în unele medii web. Ca alternativă, această acțiune trebuie efectuată utilizând o comandă din terminal.', // Recycle Bin 'recycle_bin' => 'Coș de gunoi', @@ -161,7 +161,7 @@ return [ 'roles_system_warning' => 'Fi conștient de faptul că accesul la oricare dintre cele trei permisiuni de mai sus poate permite unui utilizator să își modifice propriile privilegii sau privilegiile altor persoane din sistem. Atribuie doar roluri cu aceste permisiuni utilizatorilor de încredere.', 'role_asset_desc' => 'Aceste permisiuni controlează accesul implicit la activele din sistem. Permisiunile pe Cărți, Capitole și Pagini vor suprascrie aceste permisiuni.', 'role_asset_admins' => 'Administratorilor li se acordă automat acces la tot conținutul, dar aceste opțiuni pot afișa sau ascunde opțiunile UI.', - 'role_asset_image_view_note' => 'This relates to visibility within the image manager. Actual access of uploaded image files will be dependant upon system image storage option.', + 'role_asset_image_view_note' => 'Acest lucru se referă la vizibilitatea în managerul de imagini. Accesul efectiv al fișierelor de imagine încărcate va depinde de opțiunea de stocare a imaginilor din sistem.', 'role_all' => 'Tot', 'role_own' => 'Propriu', 'role_controlled_by_asset' => 'Controlat de activele pe care sunt încărcate', diff --git a/resources/lang/ru/editor.php b/resources/lang/ru/editor.php index d39eb5682..12399e3b4 100644 --- a/resources/lang/ru/editor.php +++ b/resources/lang/ru/editor.php @@ -66,6 +66,7 @@ return [ 'insert_link_title' => 'Вставить/Редактировать ссылку', 'insert_horizontal_line' => 'Вставить горизонтальную линию', 'insert_code_block' => 'Вставить блок кода', + 'edit_code_block' => 'Edit code block', 'insert_drawing' => 'Вставить/редактировать схему', 'drawing_manager' => 'Менеджер схем', 'insert_media' => 'Вставить/редактировать медиафайл', diff --git a/resources/lang/ru/entities.php b/resources/lang/ru/entities.php index d195a3d5c..2121f5a8c 100644 --- a/resources/lang/ru/entities.php +++ b/resources/lang/ru/entities.php @@ -42,10 +42,14 @@ return [ // Permissions and restrictions 'permissions' => 'Разрешения', - 'permissions_intro' => 'После включения опции эти разрешения будут иметь приоритет над любыми установленными разрешениями роли.', - 'permissions_enable' => 'Включение пользовательских разрешений', + 'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', 'permissions_save' => 'Сохранить разрешения', 'permissions_owner' => 'Владелец', + 'permissions_role_everyone_else' => 'Everyone Else', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Override permissions for role', // Search 'search_results' => 'Результаты поиска', diff --git a/resources/lang/sk/editor.php b/resources/lang/sk/editor.php index da7ffcf01..faf351da2 100644 --- a/resources/lang/sk/editor.php +++ b/resources/lang/sk/editor.php @@ -66,6 +66,7 @@ return [ 'insert_link_title' => 'Insert/Edit Link', 'insert_horizontal_line' => 'Insert horizontal line', 'insert_code_block' => 'Insert code block', + 'edit_code_block' => 'Edit code block', 'insert_drawing' => 'Insert/edit drawing', 'drawing_manager' => 'Drawing manager', 'insert_media' => 'Insert/edit media', diff --git a/resources/lang/sk/entities.php b/resources/lang/sk/entities.php index 88e052a22..c64756761 100644 --- a/resources/lang/sk/entities.php +++ b/resources/lang/sk/entities.php @@ -42,10 +42,14 @@ return [ // 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_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', 'permissions_save' => 'Uložiť oprávnenia', 'permissions_owner' => 'Vlastník', + 'permissions_role_everyone_else' => 'Everyone Else', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Override permissions for role', // Search 'search_results' => 'Výsledky hľadania', diff --git a/resources/lang/sl/editor.php b/resources/lang/sl/editor.php index da7ffcf01..faf351da2 100644 --- a/resources/lang/sl/editor.php +++ b/resources/lang/sl/editor.php @@ -66,6 +66,7 @@ return [ 'insert_link_title' => 'Insert/Edit Link', 'insert_horizontal_line' => 'Insert horizontal line', 'insert_code_block' => 'Insert code block', + 'edit_code_block' => 'Edit code block', 'insert_drawing' => 'Insert/edit drawing', 'drawing_manager' => 'Drawing manager', 'insert_media' => 'Insert/edit media', diff --git a/resources/lang/sl/entities.php b/resources/lang/sl/entities.php index 6e6d89734..2a6c721cb 100644 --- a/resources/lang/sl/entities.php +++ b/resources/lang/sl/entities.php @@ -42,10 +42,14 @@ return [ // Permissions and restrictions 'permissions' => 'Dovoljenja', - 'permissions_intro' => 'V trenutku, ko bodo omogočena, bodo imela ta dovoljenja prednost pred dovoljenji za določanje vlog.', - 'permissions_enable' => 'Omogoči dovoljenja po meri', + 'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', 'permissions_save' => 'Shrani dovoljenja', 'permissions_owner' => 'Lastnik', + 'permissions_role_everyone_else' => 'Everyone Else', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Override permissions for role', // Search 'search_results' => 'Rezultati iskanja', diff --git a/resources/lang/sv/activities.php b/resources/lang/sv/activities.php index 1d913b799..f730bf5ae 100644 --- a/resources/lang/sv/activities.php +++ b/resources/lang/sv/activities.php @@ -7,65 +7,65 @@ return [ // Pages 'page_create' => 'skapade sidan', - 'page_create_notification' => 'Page successfully created', + 'page_create_notification' => 'Sidan har skapats', 'page_update' => 'uppdaterade sidan', - 'page_update_notification' => 'Page successfully updated', + 'page_update_notification' => 'Sidan har uppdaterats', 'page_delete' => 'tog bort sidan', - 'page_delete_notification' => 'Page successfully deleted', + 'page_delete_notification' => 'Sidan har tagits bort', 'page_restore' => 'återställde sidan', - 'page_restore_notification' => 'Page successfully restored', + 'page_restore_notification' => 'Sidan har återställts', 'page_move' => 'flyttade sidan', // Chapters 'chapter_create' => 'skapade kapitlet', - 'chapter_create_notification' => 'Chapter successfully created', + 'chapter_create_notification' => 'Kapitlet har skapats', 'chapter_update' => 'uppdaterade kapitlet', - 'chapter_update_notification' => 'Chapter successfully updated', + 'chapter_update_notification' => 'Kapitlet har uppdaterats', 'chapter_delete' => 'tog bort kapitlet', - 'chapter_delete_notification' => 'Chapter successfully deleted', + 'chapter_delete_notification' => 'Kapitlet har tagits bort', 'chapter_move' => 'flyttade kapitlet', // Books 'book_create' => 'skapade boken', - 'book_create_notification' => 'Book successfully created', - 'book_create_from_chapter' => 'converted chapter to book', - 'book_create_from_chapter_notification' => 'Chapter successfully converted to a book', + 'book_create_notification' => 'Boken har skapats', + 'book_create_from_chapter' => 'konverterade kapitel till bok', + 'book_create_from_chapter_notification' => 'Kapitlet har konverterats till en bok', 'book_update' => 'uppdaterade boken', - 'book_update_notification' => 'Book successfully updated', + 'book_update_notification' => 'Boken har uppdaterats', 'book_delete' => 'tog bort boken', - 'book_delete_notification' => 'Book successfully deleted', + 'book_delete_notification' => 'Boken har tagits bort', 'book_sort' => 'sorterade boken', - 'book_sort_notification' => 'Book successfully re-sorted', + 'book_sort_notification' => 'Boken har sorterats om', // Bookshelves - 'bookshelf_create' => 'created shelf', - 'bookshelf_create_notification' => 'Shelf successfully created', - 'bookshelf_create_from_book' => 'converted book to shelf', - 'bookshelf_create_from_book_notification' => 'Book successfully converted to a shelf', - 'bookshelf_update' => 'updated shelf', - 'bookshelf_update_notification' => 'Shelf successfully updated', - 'bookshelf_delete' => 'deleted shelf', - 'bookshelf_delete_notification' => 'Shelf successfully deleted', + 'bookshelf_create' => 'skapade hylla', + 'bookshelf_create_notification' => 'Hyllan har skapats', + 'bookshelf_create_from_book' => 'konverterade bok till hylla', + 'bookshelf_create_from_book_notification' => 'Boken har konverterats till en hylla', + 'bookshelf_update' => 'uppdaterade hyllan', + 'bookshelf_update_notification' => 'Hyllan har uppdaterats', + 'bookshelf_delete' => 'raderade hyllan', + 'bookshelf_delete_notification' => 'Hyllan har tagits bort', // Favourites 'favourite_add_notification' => '":name" har lagts till i dina favoriter', 'favourite_remove_notification' => '":name" har tagits bort från dina favoriter', // MFA - 'mfa_setup_method_notification' => 'Multi-factor method successfully configured', - 'mfa_remove_method_notification' => 'Multi-factor method successfully removed', + 'mfa_setup_method_notification' => 'Multifaktor-metod har konfigurerats', + 'mfa_remove_method_notification' => 'Multifaktor-metod har tagits bort', // Webhooks - 'webhook_create' => 'created webhook', - 'webhook_create_notification' => 'Webhook successfully created', - 'webhook_update' => 'updated webhook', - 'webhook_update_notification' => 'Webhook successfully updated', - 'webhook_delete' => 'deleted webhook', - 'webhook_delete_notification' => 'Webhook successfully deleted', + 'webhook_create' => 'skapade webhook', + 'webhook_create_notification' => 'Webhook har skapats', + 'webhook_update' => 'uppdaterade webhook', + 'webhook_update_notification' => 'Webhook har uppdaterats', + 'webhook_delete' => 'raderade webhook', + 'webhook_delete_notification' => 'Webhook har tagits bort', // Users - 'user_update_notification' => 'User successfully updated', - 'user_delete_notification' => 'User successfully removed', + 'user_update_notification' => 'Användaren har uppdaterats', + 'user_delete_notification' => 'Användaren har tagits bort', // Other 'commented_on' => 'kommenterade', diff --git a/resources/lang/sv/auth.php b/resources/lang/sv/auth.php index 849918cdc..a456c7d21 100644 --- a/resources/lang/sv/auth.php +++ b/resources/lang/sv/auth.php @@ -21,7 +21,7 @@ return [ 'email' => 'E-post', 'password' => 'Lösenord', 'password_confirm' => 'Bekräfta lösenord', - 'password_hint' => 'Must be at least 8 characters', + 'password_hint' => 'Måste vara minst 8 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.', @@ -39,9 +39,9 @@ return [ 'register_success' => 'Tack för din registrering! Du är nu registerad och inloggad.', // Login auto-initiation - 'auto_init_starting' => 'Attempting Login', - 'auto_init_starting_desc' => 'We\'re contacting your authentication system to start the login process. If there\'s no progress after 5 seconds you can try clicking the link below.', - 'auto_init_start_link' => 'Proceed with authentication', + 'auto_init_starting' => 'Försöker Logga In', + 'auto_init_starting_desc' => 'Vi kontaktar ditt autentiseringssystem för att starta inloggningsprocessen. Om inget händer efter 5 sekunder kan du prova att klicka på länken nedan.', + 'auto_init_start_link' => 'Fortsätt med autentisering', // Password Reset 'reset_password' => 'Återställ lösenord', @@ -59,7 +59,7 @@ return [ 'email_confirm_text' => 'Vänligen bekräfta din e-postadress genom att klicka på knappen nedan:', 'email_confirm_action' => 'Bekräfta e-post', 'email_confirm_send_error' => 'E-posten behöver bekräftas men systemet kan inte skicka mail. Kontakta adminstratören för att kontrollera att allt är konfigurerat korrekt.', - 'email_confirm_success' => 'Your email has been confirmed! You should now be able to login using this email address.', + 'email_confirm_success' => 'Din e-postadress har bekräftats! Du bör nu kunna logga in med denna e-postadress.', 'email_confirm_resent' => 'Bekräftelsemailet har skickats på nytt, kolla din mail', 'email_not_confirmed' => 'E-posadress ej bekräftad', @@ -76,40 +76,40 @@ return [ 'user_invite_page_welcome' => 'Välkommen till :appName!', 'user_invite_page_text' => 'För att slutföra ditt konto och få åtkomst måste du ange ett lösenord som kommer att användas för att logga in på :appName vid framtida besök.', 'user_invite_page_confirm_button' => 'Bekräfta lösenord', - 'user_invite_success_login' => 'Password set, you should now be able to login using your set password to access :appName!', + 'user_invite_success_login' => 'Lösenord inställt, du bör nu kunna logga in med ditt inställda lösenord för att komma åt :appName!', // Multi-factor Authentication - 'mfa_setup' => 'Setup Multi-Factor Authentication', - 'mfa_setup_desc' => 'Setup multi-factor authentication as an extra layer of security for your user account.', - 'mfa_setup_configured' => 'Already configured', - 'mfa_setup_reconfigure' => 'Reconfigure', - 'mfa_setup_remove_confirmation' => 'Are you sure you want to remove this multi-factor authentication method?', + 'mfa_setup' => 'Konfigurera multifaktorsautentisering', + 'mfa_setup_desc' => 'Konfigurera multifaktorsautentisering som ett extra skydd för ditt konto.', + 'mfa_setup_configured' => 'Redan konfigurerad', + 'mfa_setup_reconfigure' => 'Omkonfigurera', + 'mfa_setup_remove_confirmation' => 'Är du säker på att du vill ta bort denna multifaktorautentiseringsmetod?', 'mfa_setup_action' => 'Setup', - 'mfa_backup_codes_usage_limit_warning' => 'You have less than 5 backup codes remaining, Please generate and store a new set before you run out of codes to prevent being locked out of your account.', - 'mfa_option_totp_title' => 'Mobile App', - 'mfa_option_totp_desc' => 'To use multi-factor authentication you\'ll need a mobile application that supports TOTP such as Google Authenticator, Authy or Microsoft Authenticator.', - 'mfa_option_backup_codes_title' => 'Backup Codes', - 'mfa_option_backup_codes_desc' => 'Securely store a set of one-time-use backup codes which you can enter to verify your identity.', - 'mfa_gen_confirm_and_enable' => 'Confirm and Enable', - 'mfa_gen_backup_codes_title' => 'Backup Codes Setup', - 'mfa_gen_backup_codes_desc' => 'Store the below list of codes in a safe place. When accessing the system you\'ll be able to use one of the codes as a second authentication mechanism.', - 'mfa_gen_backup_codes_download' => 'Download Codes', - 'mfa_gen_backup_codes_usage_warning' => 'Each code can only be used once', - 'mfa_gen_totp_title' => 'Mobile App Setup', - 'mfa_gen_totp_desc' => 'To use multi-factor authentication you\'ll need a mobile application that supports TOTP such as Google Authenticator, Authy or Microsoft Authenticator.', - 'mfa_gen_totp_scan' => 'Scan the QR code below using your preferred authentication app to get started.', - 'mfa_gen_totp_verify_setup' => 'Verify Setup', - 'mfa_gen_totp_verify_setup_desc' => 'Verify that all is working by entering a code, generated within your authentication app, in the input box below:', - 'mfa_gen_totp_provide_code_here' => 'Provide your app generated code here', - 'mfa_verify_access' => 'Verify Access', - 'mfa_verify_access_desc' => 'Your user account requires you to confirm your identity via an additional level of verification before you\'re granted access. Verify using one of your configured methods to continue.', - 'mfa_verify_no_methods' => 'No Methods Configured', - 'mfa_verify_no_methods_desc' => 'No multi-factor authentication methods could be found for your account. You\'ll need to set up at least one method before you gain access.', - 'mfa_verify_use_totp' => 'Verify using a mobile app', - 'mfa_verify_use_backup_codes' => 'Verify using a backup code', - 'mfa_verify_backup_code' => 'Backup Code', - 'mfa_verify_backup_code_desc' => 'Enter one of your remaining backup codes below:', - 'mfa_verify_backup_code_enter_here' => 'Enter backup code here', - 'mfa_verify_totp_desc' => 'Enter the code, generated using your mobile app, below:', - 'mfa_setup_login_notification' => 'Multi-factor method configured, Please now login again using the configured method.', + 'mfa_backup_codes_usage_limit_warning' => 'Du har mindre än 5 reservkoder kvar, Vänligen generera och lagra en nya innan du får slut på koder för att förhindra att du inte kommer åt ditt konto.', + 'mfa_option_totp_title' => 'Mobilapp', + 'mfa_option_totp_desc' => 'För att använda multifaktorautentisering behöver du en mobil app som stöder TOTP så som Google Authenticator, Authy eller Microsoft Authenticator.', + 'mfa_option_backup_codes_title' => 'Reservkoder', + 'mfa_option_backup_codes_desc' => 'Lagra säkert en uppsättning engångsreservkoder som du kan ange för att verifiera din identitet.', + 'mfa_gen_confirm_and_enable' => 'Bekräfta och aktivera', + 'mfa_gen_backup_codes_title' => 'Konfiguration av reservkoder', + 'mfa_gen_backup_codes_desc' => 'Spara nedanstående koder på en säker plats. När du använder systemet kommer du att kunna använda en av koderna som en andra autentiseringsmekanism.', + 'mfa_gen_backup_codes_download' => 'Ladda ner koder', + 'mfa_gen_backup_codes_usage_warning' => 'Varje kod kan endast användas en gång', + 'mfa_gen_totp_title' => 'Konfiguration av mobilapp', + 'mfa_gen_totp_desc' => 'För att använda multifaktorautentisering behöver du en mobil app som stöder TOTP så som Google Authenticator, Authy eller Microsoft Authenticator.', + 'mfa_gen_totp_scan' => 'Skanna QR-koden nedan med din föredragna autentiseringsapp för att komma igång.', + 'mfa_gen_totp_verify_setup' => 'Verifiera konfiguration', + 'mfa_gen_totp_verify_setup_desc' => 'Kontrollera att allt fungerar genom att ange en kod, genererad i din autentiseringsapp, i rutan nedan:', + 'mfa_gen_totp_provide_code_here' => 'Ange din appgenererade kod här', + 'mfa_verify_access' => 'Verifiera åtkomst', + 'mfa_verify_access_desc' => 'Ditt användarkonto kräver att du bekräftar din identitet via en ytterligare verifieringsmetod innan du får tillgång. Verifiera genom en av dina konfigurerade metoder för att fortsätta.', + 'mfa_verify_no_methods' => 'Inga metoder konfigurerade', + 'mfa_verify_no_methods_desc' => 'Inga multifaktorautentiseringsmetoder kunde hittas för ditt konto. Du måste konfigurera minst en metod innan du får tillgång.', + 'mfa_verify_use_totp' => 'Verifiera med en mobilapp', + 'mfa_verify_use_backup_codes' => 'Verifiera med en reservkod', + 'mfa_verify_backup_code' => 'Reservkod', + 'mfa_verify_backup_code_desc' => 'Ange en av dina återstående reservkoder nedan:', + 'mfa_verify_backup_code_enter_here' => 'Ange reservkod här', + 'mfa_verify_totp_desc' => 'Ange koden, som genereras med din mobilapp, nedan:', + 'mfa_setup_login_notification' => 'Multifaktormetod konfigurerad, Logga nu in igen med den konfigurerade metoden.', ]; diff --git a/resources/lang/sv/common.php b/resources/lang/sv/common.php index 3f920500d..cc81b71d8 100644 --- a/resources/lang/sv/common.php +++ b/resources/lang/sv/common.php @@ -39,16 +39,16 @@ return [ 'reset' => 'Återställ', 'remove' => 'Radera', 'add' => 'Lägg till', - 'configure' => 'Configure', + 'configure' => 'Konfigurera', 'fullscreen' => 'Helskärm', 'favourite' => 'Favorit', 'unfavourite' => 'Ta bort favorit', 'next' => 'Nästa', 'previous' => 'Föregående', - 'filter_active' => 'Active Filter:', - 'filter_clear' => 'Clear Filter', - 'download' => 'Download', - 'open_in_tab' => 'Open in Tab', + 'filter_active' => 'Aktivt filter:', + 'filter_clear' => 'Rensa filter', + 'download' => 'Ladda ner', + 'open_in_tab' => 'Öppna i flik', // Sort Options 'sort_options' => 'Sorteringsalternativ', @@ -65,7 +65,7 @@ return [ 'no_activity' => 'Ingen aktivitet att visa', 'no_items' => 'Inga tillgängliga föremål', 'back_to_top' => 'Tillbaka till toppen', - 'skip_to_main_content' => 'Skip to main content', + 'skip_to_main_content' => 'Hoppa till huvudinnehåll', 'toggle_details' => 'Växla detaljer', 'toggle_thumbnails' => 'Växla miniatyrer', 'details' => 'Information', @@ -74,10 +74,10 @@ return [ 'default' => 'Förvald', 'breadcrumb' => 'Brödsmula', 'status' => 'Status', - 'status_active' => 'Active', + 'status_active' => 'Aktiv', 'status_inactive' => 'Inaktiv', - 'never' => 'Never', - 'none' => 'None', + 'never' => 'Aldrig', + 'none' => 'Inga', // Header 'header_menu_expand' => 'Expandera sidhuvudsmenyn', diff --git a/resources/lang/sv/editor.php b/resources/lang/sv/editor.php index da7ffcf01..8acc84a4b 100644 --- a/resources/lang/sv/editor.php +++ b/resources/lang/sv/editor.php @@ -7,165 +7,166 @@ */ return [ // General editor terms - 'general' => 'General', - 'advanced' => 'Advanced', - 'none' => 'None', - 'cancel' => 'Cancel', - 'save' => 'Save', - 'close' => 'Close', - 'undo' => 'Undo', - 'redo' => 'Redo', - 'left' => 'Left', - 'center' => 'Center', - 'right' => 'Right', - 'top' => 'Top', - 'middle' => 'Middle', - 'bottom' => 'Bottom', - 'width' => 'Width', - 'height' => 'Height', - 'More' => 'More', - 'select' => 'Select...', + 'general' => 'Allmän', + 'advanced' => 'Avancerad', + 'none' => 'Inga', + 'cancel' => 'Avbryt', + 'save' => 'Spara', + 'close' => 'Stäng', + 'undo' => 'Ångra', + 'redo' => 'Gör om', + 'left' => 'Vänster', + 'center' => 'Mitten', + 'right' => 'Höger', + 'top' => 'Topp', + 'middle' => 'Mitt', + 'bottom' => 'Botten', + 'width' => 'Bredd', + 'height' => 'Höjd', + 'More' => 'Mer', + 'select' => 'Välj...', // Toolbar - 'formats' => 'Formats', - 'header_large' => 'Large Header', - 'header_medium' => 'Medium Header', - 'header_small' => 'Small Header', - 'header_tiny' => 'Tiny Header', - 'paragraph' => 'Paragraph', - 'blockquote' => 'Blockquote', - 'inline_code' => 'Inline code', - 'callouts' => 'Callouts', + 'formats' => 'Format', + 'header_large' => 'Stor rubrik', + 'header_medium' => 'Mellanstor rubrik', + 'header_small' => 'Mindre rubrik', + 'header_tiny' => 'Liten rubrik', + 'paragraph' => 'Paragraf', + 'blockquote' => 'Blockcitat', + 'inline_code' => 'Inline-kod', + 'callouts' => 'Anslag', 'callout_information' => 'Information', 'callout_success' => 'Success', - 'callout_warning' => 'Warning', - 'callout_danger' => 'Danger', - 'bold' => 'Bold', - 'italic' => 'Italic', - 'underline' => 'Underline', - 'strikethrough' => 'Strikethrough', - 'superscript' => 'Superscript', - 'subscript' => 'Subscript', - 'text_color' => 'Text color', - 'custom_color' => 'Custom color', - 'remove_color' => 'Remove color', - 'background_color' => 'Background color', - 'align_left' => 'Align left', - 'align_center' => 'Align center', - 'align_right' => 'Align right', - 'align_justify' => 'Justify', - 'list_bullet' => 'Bullet list', - 'list_numbered' => 'Numbered list', - 'list_task' => 'Task list', - 'indent_increase' => 'Increase indent', - 'indent_decrease' => 'Decrease indent', - 'table' => 'Table', - 'insert_image' => 'Insert image', - 'insert_image_title' => 'Insert/Edit Image', - 'insert_link' => 'Insert/edit link', - 'insert_link_title' => 'Insert/Edit Link', - 'insert_horizontal_line' => 'Insert horizontal line', - 'insert_code_block' => 'Insert code block', - 'insert_drawing' => 'Insert/edit drawing', - 'drawing_manager' => 'Drawing manager', - 'insert_media' => 'Insert/edit media', - 'insert_media_title' => 'Insert/Edit Media', - 'clear_formatting' => 'Clear formatting', - 'source_code' => 'Source code', - 'source_code_title' => 'Source Code', - 'fullscreen' => 'Fullscreen', - 'image_options' => 'Image options', + 'callout_warning' => 'Varning', + 'callout_danger' => 'Fara', + 'bold' => 'Fetstil', + 'italic' => 'Kursiv', + 'underline' => 'Understruken', + 'strikethrough' => 'Genomstruken', + 'superscript' => 'Upphöjd', + 'subscript' => 'Nedsänkt', + 'text_color' => 'Textfärg', + 'custom_color' => 'Anpassad färg', + 'remove_color' => 'Ta bort färg', + 'background_color' => 'Bakgrundsfärg', + 'align_left' => 'Vänsterjustera', + 'align_center' => 'Centrera', + 'align_right' => 'Högerjustera', + 'align_justify' => 'Marginaljustera', + 'list_bullet' => 'Punktlista', + 'list_numbered' => 'Numrerad lista', + 'list_task' => 'Checklista', + 'indent_increase' => 'Öka indrag', + 'indent_decrease' => 'Minska indrag', + 'table' => 'Tabell', + 'insert_image' => 'Infoga bild', + 'insert_image_title' => 'Infoga/redigera bild', + 'insert_link' => 'Infoga/redigera länk', + 'insert_link_title' => 'Infoga/redigera länk', + 'insert_horizontal_line' => 'Infoga horisontell linje', + 'insert_code_block' => 'Infoga kodblock', + 'edit_code_block' => 'Redigera kodblock', + 'insert_drawing' => 'Infoga/redigera ritning', + 'drawing_manager' => 'Ritningshanterare', + 'insert_media' => 'Infoga/redigera media', + 'insert_media_title' => 'Infoga/redigera media', + 'clear_formatting' => 'Rensa formatering', + 'source_code' => 'Källkod', + 'source_code_title' => 'Källkod', + 'fullscreen' => 'Helskärm', + 'image_options' => 'Bildalternativ', // Tables - 'table_properties' => 'Table properties', - 'table_properties_title' => 'Table Properties', - 'delete_table' => 'Delete table', - 'insert_row_before' => 'Insert row before', - 'insert_row_after' => 'Insert row after', - 'delete_row' => 'Delete row', - 'insert_column_before' => 'Insert column before', - 'insert_column_after' => 'Insert column after', - 'delete_column' => 'Delete column', + 'table_properties' => 'Tabellegenskaper', + 'table_properties_title' => 'Tabellegenskaper', + 'delete_table' => 'Ta bort tabell', + 'insert_row_before' => 'Infoga rad före', + 'insert_row_after' => 'Infoga rad efter', + 'delete_row' => 'Ta bort rad', + 'insert_column_before' => 'Infoga kolumn före', + 'insert_column_after' => 'Infoga kolumn efter', + 'delete_column' => 'Ta bort kolumn', 'table_cell' => 'Cell', - 'table_row' => 'Row', - 'table_column' => 'Column', - 'cell_properties' => 'Cell properties', - 'cell_properties_title' => 'Cell Properties', - 'cell_type' => 'Cell type', + 'table_row' => 'Rad', + 'table_column' => 'Kolumn', + 'cell_properties' => 'Cellegenskaper', + 'cell_properties_title' => 'Cellegenskaper', + 'cell_type' => 'Celltyp', 'cell_type_cell' => 'Cell', - 'cell_scope' => 'Scope', - 'cell_type_header' => 'Header cell', - 'merge_cells' => 'Merge cells', - 'split_cell' => 'Split cell', - 'table_row_group' => 'Row Group', - 'table_column_group' => 'Column Group', - 'horizontal_align' => 'Horizontal align', - 'vertical_align' => 'Vertical align', - 'border_width' => 'Border width', - 'border_style' => 'Border style', - 'border_color' => 'Border color', - 'row_properties' => 'Row properties', - 'row_properties_title' => 'Row Properties', - 'cut_row' => 'Cut row', - 'copy_row' => 'Copy row', - 'paste_row_before' => 'Paste row before', - 'paste_row_after' => 'Paste row after', - 'row_type' => 'Row type', - 'row_type_header' => 'Header', - 'row_type_body' => 'Body', - 'row_type_footer' => 'Footer', - 'alignment' => 'Alignment', - 'cut_column' => 'Cut column', - 'copy_column' => 'Copy column', - 'paste_column_before' => 'Paste column before', - 'paste_column_after' => 'Paste column after', + 'cell_scope' => 'Omfattning', + 'cell_type_header' => 'Rubrikcell', + 'merge_cells' => 'Sammanfoga celler', + 'split_cell' => 'Dela cell', + 'table_row_group' => 'Radgrupp', + 'table_column_group' => 'Kolumngrupp', + 'horizontal_align' => 'Horisontell justering', + 'vertical_align' => 'Vertikal justering', + 'border_width' => 'Kantbredd', + 'border_style' => 'Kantstil', + 'border_color' => 'Kantfärg', + 'row_properties' => 'Radegenskaper', + 'row_properties_title' => 'Radegenskaper', + 'cut_row' => 'Klipp rad', + 'copy_row' => 'Kopiera rad', + 'paste_row_before' => 'Infoga rad före', + 'paste_row_after' => 'Klistra in rad efter', + 'row_type' => 'Radtyp', + 'row_type_header' => 'Rubrik', + 'row_type_body' => 'Brödtext', + 'row_type_footer' => 'Sidfot', + 'alignment' => 'Justering', + 'cut_column' => 'Klipp kolumn', + 'copy_column' => 'Kopiera kolumn', + 'paste_column_before' => 'Infoga kolumn före', + 'paste_column_after' => 'Infoga kolumn före', 'cell_padding' => 'Cell padding', 'cell_spacing' => 'Cell spacing', 'caption' => 'Caption', 'show_caption' => 'Show caption', - 'constrain' => 'Constrain proportions', + 'constrain' => 'Begränsa proportioner', 'cell_border_solid' => 'Solid', - 'cell_border_dotted' => 'Dotted', - 'cell_border_dashed' => 'Dashed', - 'cell_border_double' => 'Double', + 'cell_border_dotted' => 'Punktad', + 'cell_border_dashed' => 'Streckad', + 'cell_border_double' => 'Dubbel', 'cell_border_groove' => 'Groove', 'cell_border_ridge' => 'Ridge', - 'cell_border_inset' => 'Inset', - 'cell_border_outset' => 'Outset', - 'cell_border_none' => 'None', - 'cell_border_hidden' => 'Hidden', + 'cell_border_inset' => 'Infälld', + 'cell_border_outset' => 'Utfälld', + 'cell_border_none' => 'Ingen', + 'cell_border_hidden' => 'Dold', // Images, links, details/summary & embed - 'source' => 'Source', - 'alt_desc' => 'Alternative description', - 'embed' => 'Embed', - 'paste_embed' => 'Paste your embed code below:', + 'source' => 'Källa', + 'alt_desc' => 'Alternativ beskrivning', + 'embed' => 'Bädda in', + 'paste_embed' => 'Klistra in din inbäddningskod nedan:', 'url' => 'URL', - 'text_to_display' => 'Text to display', - 'title' => 'Title', - 'open_link' => 'Open link in...', - 'open_link_current' => 'Current window', - 'open_link_new' => 'New window', - 'insert_collapsible' => 'Insert collapsible block', - 'collapsible_unwrap' => 'Unwrap', - 'edit_label' => 'Edit label', - 'toggle_open_closed' => 'Toggle open/closed', - 'collapsible_edit' => 'Edit collapsible block', - 'toggle_label' => 'Toggle label', + 'text_to_display' => 'Text som ska visas', + 'title' => 'Titel', + 'open_link' => 'Öppna länk i...', + 'open_link_current' => 'Aktuellt fönster', + 'open_link_new' => 'Nytt fönster', + 'insert_collapsible' => 'Infoga hopfällbart block', + 'collapsible_unwrap' => 'Expandera', + 'edit_label' => 'Redigera etikett', + 'toggle_open_closed' => 'Växla mellan öppen/stängd', + 'collapsible_edit' => 'Redigera hopfällbart block', + 'toggle_label' => 'Visa eller dölj etikett', // About view 'about' => 'About the editor', - 'about_title' => 'About the WYSIWYG Editor', + 'about_title' => 'Om WYSIWYG redigeraren', 'editor_license' => 'Editor License & Copyright', - 'editor_tiny_license' => 'This editor is built using :tinyLink which is provided under the MIT license.', - 'editor_tiny_license_link' => 'The copyright and license details of TinyMCE can be found here.', - 'save_continue' => 'Save Page & Continue', - 'callouts_cycle' => '(Keep pressing to toggle through types)', + 'editor_tiny_license' => 'Denna redigerare är byggd med :tinyLink som tillhandahålls under MIT licensen.', + 'editor_tiny_license_link' => 'Upphovsrätten och licensuppgifterna för TinyMCE hittar du här.', + 'save_continue' => 'Spara & Fortsätt', + 'callouts_cycle' => '(Fortsätt trycka för att växla mellan typer)', 'link_selector' => 'Link to content', - 'shortcuts' => 'Shortcuts', - 'shortcut' => 'Shortcut', - 'shortcuts_intro' => 'The following shortcuts are available in the editor:', + 'shortcuts' => 'Genvägar', + 'shortcut' => 'Genväg', + 'shortcuts_intro' => 'Följande genvägar finns i redigeraren:', 'windows_linux' => '(Windows/Linux)', 'mac' => '(Mac)', - 'description' => 'Description', + 'description' => 'Beskrivning', ]; diff --git a/resources/lang/sv/entities.php b/resources/lang/sv/entities.php index afa94c9ed..74957a9e4 100644 --- a/resources/lang/sv/entities.php +++ b/resources/lang/sv/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Uppdaterad :timeLength', 'meta_updated_name' => 'Uppdaterad :timeLength av :user', 'meta_owned_name' => 'Ägs av :user', - 'meta_reference_page_count' => 'Referenced on 1 page|Referenced on :count pages', + 'meta_reference_page_count' => 'Referas till på en sida|Refereras till på :count sidor', 'entity_select' => 'Välj enhet', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => 'Bilder', @@ -42,10 +42,14 @@ return [ // 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_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', 'permissions_save' => 'Spara rättigheter', 'permissions_owner' => 'Ägare', + 'permissions_role_everyone_else' => 'Everyone Else', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Override permissions for role', // Search 'search_results' => 'Sökresultat', @@ -352,27 +356,27 @@ return [ 'revision_cannot_delete_latest' => 'Det går inte att ta bort den senaste versionen.', // Copy view - 'copy_consider' => 'Please consider the below when copying content.', - 'copy_consider_permissions' => 'Custom permission settings will not be copied.', - 'copy_consider_owner' => 'You will become the owner of all copied content.', - 'copy_consider_images' => 'Page image files will not be duplicated & the original images will retain their relation to the page they were originally uploaded to.', - 'copy_consider_attachments' => 'Page attachments will not be copied.', - 'copy_consider_access' => 'A change of location, owner or permissions may result in this content being accessible to those previously without access.', + 'copy_consider' => 'Tänk på nedan när du kopierar innehåll.', + 'copy_consider_permissions' => 'Anpassade behörighetsinställningar kommer inte att kopieras.', + 'copy_consider_owner' => 'Du kommer att bli ägare till allt kopierat innehåll.', + 'copy_consider_images' => 'Bildfiler för sidan kommer inte att dupliceras och de ursprungliga bilderna kommer att behålla sin relation till den sida de ursprungligen laddades upp till.', + 'copy_consider_attachments' => 'Sidans bifogade filer kommer inte att kopieras.', + 'copy_consider_access' => 'Ändring av plats, ägare eller behörigheter kan leda till att detta innehåll blir tillgängligt för dem som tidigare inte haft åtkomst.', // Conversions - 'convert_to_shelf' => 'Convert to Shelf', - 'convert_to_shelf_contents_desc' => 'You can convert this book to a new shelf with the same contents. Chapters contained within this book will be converted to new books. If this book contains any pages, that are not in a chapter, this book will be renamed and contain such pages, and this book will become part of the new shelf.', - 'convert_to_shelf_permissions_desc' => 'Any permissions set on this book will be copied to the new shelf and to all new child books that don\'t have their own permissions enforced. Note that permissions on shelves do not auto-cascade to content within, as they do for books.', - 'convert_book' => 'Convert Book', - 'convert_book_confirm' => 'Are you sure you want to convert this book?', - 'convert_undo_warning' => 'This cannot be as easily undone.', - 'convert_to_book' => 'Convert to Book', - 'convert_to_book_desc' => 'You can convert this chapter to a new book with the same contents. Any permissions set on this chapter will be copied to the new book but any inherited permissions, from the parent book, will not be copied which could lead to a change of access control.', - 'convert_chapter' => 'Convert Chapter', - 'convert_chapter_confirm' => 'Are you sure you want to convert this chapter?', + 'convert_to_shelf' => 'Konvertera till hylla', + 'convert_to_shelf_contents_desc' => 'Du kan konvertera denna bok till en ny hylla med samma innehåll. Kapitlen inom denna bok konverteras till nya böcker. Om denna bok innehåller sidor som inte är i ett kapitel så kommer denna bok att döpas om och innehålla dessa sidor. Denna bok blir då en del av den nya hyllan.', + 'convert_to_shelf_permissions_desc' => 'Alla behörigheter som ställs in på denna bok kommer att kopieras till den nya hyllan och till alla nya underböcker som inte har egna behörigheter applicerade. Observera att behörigheter på hyllor inte automatisk ärvs av innehåll inom hyllan, så som med böcker.', + 'convert_book' => 'Konvertera bok', + 'convert_book_confirm' => 'Är du säker på att du vill konvertera boken?', + 'convert_undo_warning' => 'Detta kan inte ångras lika lätt.', + 'convert_to_book' => 'Konvertera till bok', + 'convert_to_book_desc' => 'Du kan konvertera detta kapitel till en ny bok med samma innehåll. Eventuella behörigheter som angetts på detta kapitel kommer att kopieras till den nya boken men ärvda behörigheter från föräldraboken kommer inte att kopieras vilket kan leda till skillnader i åtkomsten.', + 'convert_chapter' => 'Konvertera kapitel', + 'convert_chapter_confirm' => 'Är du säker på att du vill konvertera det här kapitlet?', // References - 'references' => 'References', - 'references_none' => 'There are no tracked references to this item.', - 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + 'references' => 'Referenser', + 'references_none' => 'Det finns inga referenser kopplade till detta objekt.', + 'references_to_desc' => 'Nedan visas alla kända sidor i systemet som länkar till detta objekt.', ]; diff --git a/resources/lang/sv/errors.php b/resources/lang/sv/errors.php index 482fbc21f..dfd13f25d 100644 --- a/resources/lang/sv/errors.php +++ b/resources/lang/sv/errors.php @@ -23,10 +23,10 @@ return [ 'saml_no_email_address' => 'Kunde inte hitta en e-postadress för den här användaren i data som tillhandahålls av det externa autentiseringssystemet', 'saml_invalid_response_id' => 'En begäran från det externa autentiseringssystemet känns inte igen av en process som startats av denna applikation. Att navigera bakåt efter en inloggning kan orsaka detta problem.', 'saml_fail_authed' => 'Inloggning med :system misslyckades, systemet godkände inte auktoriseringen', - 'oidc_already_logged_in' => 'Already logged in', - 'oidc_user_not_registered' => 'The user :name is not registered and automatic registration is disabled', - 'oidc_no_email_address' => 'Could not find an email address, for this user, in the data provided by the external authentication system', - 'oidc_fail_authed' => 'Login using :system failed, system did not provide successful authorization', + 'oidc_already_logged_in' => 'Redan inloggad', + 'oidc_user_not_registered' => 'Användaren :name är inte registrerad och automatisk registrering är inaktiverad', + 'oidc_no_email_address' => 'Kunde inte hitta en e-postadress för den här användaren i den data som tillhandahölls av det externa autentiseringssystemet', + 'oidc_fail_authed' => 'Inloggning med :system misslyckades, systemet presenterade inte en godkänd auktorisering', 'social_no_action_defined' => 'Ingen åtgärd definierad', 'social_login_bad_response' => "Ett fel inträffade vid inloggning genom :socialAccount: \n:error", 'social_account_in_use' => 'Detta konto från :socialAccount används redan. Testa att logga in med :socialAccount istället.', @@ -58,7 +58,7 @@ return [ // Entities 'entity_not_found' => 'Innehållet hittades inte', - 'bookshelf_not_found' => 'Shelf not found', + 'bookshelf_not_found' => 'Hyllan hittades inte', 'book_not_found' => 'Boken hittades inte', 'page_not_found' => 'Sidan hittades inte', 'chapter_not_found' => 'Kapitlet hittades inte', diff --git a/resources/lang/sv/settings.php b/resources/lang/sv/settings.php index f27605454..cc4a280be 100644 --- a/resources/lang/sv/settings.php +++ b/resources/lang/sv/settings.php @@ -10,8 +10,8 @@ return [ 'settings' => 'Inställningar', 'settings_save' => 'Spara inställningar', 'settings_save_success' => 'Inställningarna har sparats', - 'system_version' => 'System Version', - 'categories' => 'Categories', + 'system_version' => 'Systemversion', + 'categories' => 'Kategorier', // App Settings 'app_customization' => 'Sidanpassning', @@ -27,8 +27,8 @@ return [ 'app_secure_images' => 'Aktivera högre säkerhet för bilduppladdningar?', 'app_secure_images_toggle' => 'Aktivera säkrare bilduppladdningar', 'app_secure_images_desc' => 'Av prestandaskäl är alla bilder publika. Det här alternativet lägger till en slumpmässig, svårgissad sträng framför alla bild-URL:er. Se till att kataloglistning inte är aktivt för att förhindra åtkomst.', - 'app_default_editor' => 'Default Page Editor', - 'app_default_editor_desc' => 'Select which editor will be used by default when editing new pages. This can be overridden at a page level where permissions allow.', + 'app_default_editor' => 'Standard sidredigerare', + 'app_default_editor_desc' => 'Välj vilken redigerare som ska användas som standard vid redigering av nya sidor. Detta kan ändras enskilt för sidor som tillåter det.', 'app_custom_html' => 'Egen HTML i ', 'app_custom_html_desc' => 'Eventuellt innehåll i det här fältet placeras längst ner i -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' => 'Anpassat innehåll i HTML-huvud är inaktiverat på denna inställningssida för att säkerställa att eventuella ändringar som påverkar funktionaliteten kan återställas.', @@ -89,16 +89,16 @@ return [ 'maint_send_test_email_mail_text' => 'Grattis! Eftersom du fick detta e-postmeddelande verkar dina e-postinställningar vara korrekt konfigurerade.', 'maint_recycle_bin_desc' => 'Borttagna hyllor, böcker, kapitel & sidor skickas till papperskorgen så att de kan återställas eller raderas permanent. Äldre objekt i papperskorgen kan automatiskt tas bort efter ett tag beroende på systemkonfiguration.', 'maint_recycle_bin_open' => 'Öppna papperskorgen', - 'maint_regen_references' => 'Regenerate References', - 'maint_regen_references_desc' => 'This action will rebuild the cross-item reference index within the database. This is usually handled automatically but this action can be useful to index old content or content added via unofficial methods.', - 'maint_regen_references_success' => 'Reference index has been regenerated!', - 'maint_timeout_command_note' => 'Note: This action can take time to run, which can lead to timeout issues in some web environments. As an alternative, this action be performed using a terminal command.', + 'maint_regen_references' => 'Regenerera referenser', + 'maint_regen_references_desc' => 'Den här åtgärden kommer att bygga om referensindex för kopplade objekt i databasen. Detta hanteras vanligtvis automatiskt, men denna åtgärd kan vara användbar för att indexera gammalt innehåll eller innehåll som lagts till via inofficiella metoder.', + 'maint_regen_references_success' => 'Referensindex har regenererats!', + 'maint_timeout_command_note' => 'Obs: Denna åtgärd kan ta tid att köra, vilket kan leda till timeoutproblem i vissa webbmiljöer. Som ett alternativ kan denna åtgärd utföras med ett terminalkommando.', // Recycle Bin 'recycle_bin' => 'Papperskorgen', 'recycle_bin_desc' => 'Här kan du återställa objekt som har tagits bort eller välja att permanent ta bort dem från systemet. Denna lista är ofiltrerad till skillnad från liknande aktivitetslistor i systemet där behörighetsfilter tillämpas.', 'recycle_bin_deleted_item' => 'Raderat objekt', - 'recycle_bin_deleted_parent' => 'Parent', + 'recycle_bin_deleted_parent' => 'Överordnad', 'recycle_bin_deleted_by' => 'Borttagen av', 'recycle_bin_deleted_at' => 'Tid för borttagning', 'recycle_bin_permanently_delete' => 'Radera permanent', @@ -111,7 +111,7 @@ return [ 'recycle_bin_restore_list' => 'Objekt som ska återställas', 'recycle_bin_restore_confirm' => 'Denna åtgärd kommer att återställa det raderade objektet, inklusive alla underordnade element, till deras ursprungliga plats. Om den ursprungliga platsen har tagits bort sedan dess, och är nu i papperskorgen, kommer det överordnade objektet också att behöva återställas.', 'recycle_bin_restore_deleted_parent' => 'Föräldern till det här objektet har också tagits bort. Dessa kommer att förbli raderade tills den förälder är återställd.', - 'recycle_bin_restore_parent' => 'Restore Parent', + 'recycle_bin_restore_parent' => 'Återställ överordnad', 'recycle_bin_destroy_notification' => 'Raderade :count totala objekt från papperskorgen.', 'recycle_bin_restore_notification' => 'Återställt :count totala objekt från papperskorgen.', @@ -125,7 +125,7 @@ return [ 'audit_table_user' => 'Användare', 'audit_table_event' => 'Händelse', 'audit_table_related' => 'Relaterat objekt eller detalj', - 'audit_table_ip' => 'IP Address', + 'audit_table_ip' => 'IP-adress', 'audit_table_date' => 'Datum för senaste aktiviteten', 'audit_date_from' => 'Datumintervall från', 'audit_date_to' => 'Datumintervall till', @@ -145,7 +145,7 @@ return [ 'role_details' => 'Om rollen', 'role_name' => 'Rollens namn', 'role_desc' => 'Kort beskrivning av rollen', - 'role_mfa_enforced' => 'Requires Multi-Factor Authentication', + 'role_mfa_enforced' => 'Kräver multifaktorsautentisering', 'role_external_auth_id' => 'Externa autentiserings-ID:n', 'role_system' => 'Systemrättigheter', 'role_manage_users' => 'Hanter användare', @@ -155,13 +155,13 @@ return [ 'role_manage_page_templates' => 'Hantera mallar', 'role_access_api' => 'Åtkomst till systemets API', 'role_manage_settings' => 'Hantera appinställningar', - 'role_export_content' => 'Export content', - 'role_editor_change' => 'Change page editor', + 'role_export_content' => 'Exportera innehåll', + 'role_editor_change' => 'Ändra sidredigerare', 'role_asset' => 'Tillgång till innehåll', 'roles_system_warning' => 'Var medveten om att åtkomst till någon av ovanstående tre behörigheter kan tillåta en användare att ändra sina egna rättigheter eller andras rättigheter i systemet. Tilldela endast roller med dessa behörigheter till betrodda användare.', '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.', 'role_asset_admins' => 'Administratörer har automatisk tillgång till allt innehåll men dessa alternativ kan visa och dölja vissa gränssnittselement', - 'role_asset_image_view_note' => 'This relates to visibility within the image manager. Actual access of uploaded image files will be dependant upon system image storage option.', + 'role_asset_image_view_note' => 'Detta avser synlighet inom bildhanteraren. Faktisk åtkomst för uppladdade bildfiler kommer att bero på alternativ för bildlagring.', 'role_all' => 'Alla', 'role_own' => 'Egna', 'role_controlled_by_asset' => 'Kontrolleras av den sida de laddas upp till', @@ -182,7 +182,7 @@ return [ 'users_role' => 'Användarroller', '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' => 'Set a password used to log-in to the application. This must be at least 8 characters long.', + 'users_password_desc' => 'Ange ett lösenord som ska användas för att logga in på sidan. Lösenordet måste vara minst 8 tecken långt.', 'users_send_invite_text' => 'Du kan välja att skicka denna användare ett e-postmeddelande som tillåter dem att ställa in sitt eget lösenord, eller så kan du ställa in deras lösenord själv.', 'users_send_invite_option' => 'Skicka e-post med inbjudan', 'users_external_auth_id' => 'Externt ID för autentisering', @@ -213,10 +213,10 @@ return [ 'users_api_tokens_create' => 'Skapa token', 'users_api_tokens_expires' => 'Förfaller', 'users_api_tokens_docs' => 'API-dokumentation', - 'users_mfa' => 'Multi-Factor Authentication', - 'users_mfa_desc' => 'Setup multi-factor authentication as an extra layer of security for your user account.', - 'users_mfa_x_methods' => ':count method configured|:count methods configured', - 'users_mfa_configure' => 'Configure Methods', + 'users_mfa' => 'Multifaktorautentisering', + 'users_mfa_desc' => 'Konfigurera multifaktorsautentisering som ett extra skydd för ditt konto.', + 'users_mfa_x_methods' => ':count metod konfigurerad|:count metoder konfigurerade', + 'users_mfa_configure' => 'Konfigurera metoder', // API Tokens 'user_api_token_create' => 'Skapa API-nyckel', @@ -241,11 +241,11 @@ return [ // Webhooks 'webhooks' => 'Webhooks', - 'webhooks_create' => 'Create New Webhook', - 'webhooks_none_created' => 'No webhooks have yet been created.', - 'webhooks_edit' => 'Edit Webhook', - 'webhooks_save' => 'Save Webhook', - 'webhooks_details' => 'Webhook Details', + 'webhooks_create' => 'Skapa ny webhook', + 'webhooks_none_created' => 'Inga webhooks har skapats än.', + 'webhooks_edit' => 'Redigera webhook', + 'webhooks_save' => 'Spara webhook', + 'webhooks_details' => 'Webhook-detaljer', 'webhooks_details_desc' => 'Provide a user friendly name and a POST endpoint as a location for the webhook data to be sent to.', 'webhooks_events' => 'Webhook Events', 'webhooks_events_desc' => 'Select all the events that should trigger this webhook to be called.', diff --git a/resources/lang/sv/validation.php b/resources/lang/sv/validation.php index 2d7f1da9a..b9c609f48 100644 --- a/resources/lang/sv/validation.php +++ b/resources/lang/sv/validation.php @@ -15,7 +15,7 @@ return [ 'alpha_dash' => ':attribute får bara innehålla bokstäver, siffror och bindestreck.', 'alpha_num' => ':attribute får bara innehålla bokstäver och siffror.', 'array' => ':attribute måste vara en array.', - 'backup_codes' => 'The provided code is not valid or has already been used.', + 'backup_codes' => 'Den angivna koden är inte giltig eller har redan använts.', 'before' => ':attribute måste vara före :date.', 'between' => [ 'numeric' => ':attribute måste vara mellan :min och :max.', @@ -32,7 +32,7 @@ return [ 'digits_between' => ':attribute måste vara mellan :min och :max siffror.', 'email' => ':attribute måste vara en giltig e-postadress.', 'ends_with' => ':attribute måste sluta med något av följande: :values', - 'file' => 'The :attribute must be provided as a valid file.', + 'file' => ':attribute måste anges som en giltig fil.', 'filled' => ':attribute är obligatoriskt.', 'gt' => [ 'numeric' => ':attribute måste vara större än :value.', @@ -100,7 +100,7 @@ return [ ], 'string' => ':attribute måste vara en sträng.', 'timezone' => ':attribute måste vara en giltig tidszon.', - 'totp' => 'The provided code is not valid or has expired.', + 'totp' => 'Den angivna koden är inte giltig eller har löpt ut.', 'unique' => ':attribute är upptaget', 'url' => 'Formatet på :attribute är ogiltigt.', 'uploaded' => 'Filen kunde inte laddas upp. Servern kanske inte tillåter filer med denna storlek.', diff --git a/resources/lang/tr/editor.php b/resources/lang/tr/editor.php index d5a413880..7324de26a 100644 --- a/resources/lang/tr/editor.php +++ b/resources/lang/tr/editor.php @@ -66,6 +66,7 @@ return [ 'insert_link_title' => 'Bağlantı Ekle/Düzenle', 'insert_horizontal_line' => 'Yatay çizgi ekle', 'insert_code_block' => 'Kod bloğu ekle', + 'edit_code_block' => 'Edit code block', 'insert_drawing' => 'Çizim ekle/düzenle', 'drawing_manager' => 'Çizim yöneticisi', 'insert_media' => 'Medya ekle/düzenle', diff --git a/resources/lang/tr/entities.php b/resources/lang/tr/entities.php index 30cd221e3..e361b3c00 100644 --- a/resources/lang/tr/entities.php +++ b/resources/lang/tr/entities.php @@ -42,10 +42,14 @@ return [ // Permissions and restrictions 'permissions' => 'İzinler', - 'permissions_intro' => 'Etkinleştirildikten sonra bu izinler, diğer bütün izinlerden öncelikli olacaktır.', - 'permissions_enable' => 'Özelleştirilmiş Yetkileri Etkinleştir', + 'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', 'permissions_save' => 'İzinleri Kaydet', 'permissions_owner' => 'Sahip', + 'permissions_role_everyone_else' => 'Everyone Else', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Override permissions for role', // Search 'search_results' => 'Arama Sonuçları', diff --git a/resources/lang/uk/editor.php b/resources/lang/uk/editor.php index 68eb372ba..2c71dc628 100644 --- a/resources/lang/uk/editor.php +++ b/resources/lang/uk/editor.php @@ -66,6 +66,7 @@ return [ 'insert_link_title' => 'Вставити/редагувати посилання', 'insert_horizontal_line' => 'Вставити горизонтальну лінію', 'insert_code_block' => 'Вставити блок коду', + 'edit_code_block' => 'Редагування код блоку', 'insert_drawing' => 'Вставити/редагувати малюнок', 'drawing_manager' => 'Диспетчер малювання', 'insert_media' => 'Вставити/редагувати медіа', diff --git a/resources/lang/uk/entities.php b/resources/lang/uk/entities.php index a1f43c7dd..3db380b15 100644 --- a/resources/lang/uk/entities.php +++ b/resources/lang/uk/entities.php @@ -42,10 +42,14 @@ return [ // Permissions and restrictions 'permissions' => 'Дозволи', - 'permissions_intro' => 'Після ввімкнення ці дозволи будуть мати вищий пріоритет ніж інші дозволи ролей.', - 'permissions_enable' => 'Увімкнути спеціальні дозволи', + 'permissions_desc' => 'Встановіть тут дозволи, щоб перевизначити права за замовчуванням, які надаються ролями користувачів.', + 'permissions_book_cascade' => 'Дозволи, встановлені на книги будуть автоматично каскадом до дитячих глав та сторінок, якщо вони не матимуть свої дозволи.', + 'permissions_chapter_cascade' => 'Дозволи, встановлені для глав будуть автоматично каскадом на дочірні сторінки, якщо вони не матимуть своїх прав.', 'permissions_save' => 'Зберегти дозволи', 'permissions_owner' => 'Власник', + 'permissions_role_everyone_else' => 'Всі інші', + 'permissions_role_everyone_else_desc' => 'Встановити дозвіл для всіх ролей не спеціально перевизначений.', + 'permissions_role_override' => 'Змінити права доступу для ролі', // Search 'search_results' => 'Результати пошуку', diff --git a/resources/lang/uz/editor.php b/resources/lang/uz/editor.php index da7ffcf01..faf351da2 100644 --- a/resources/lang/uz/editor.php +++ b/resources/lang/uz/editor.php @@ -66,6 +66,7 @@ return [ 'insert_link_title' => 'Insert/Edit Link', 'insert_horizontal_line' => 'Insert horizontal line', 'insert_code_block' => 'Insert code block', + 'edit_code_block' => 'Edit code block', 'insert_drawing' => 'Insert/edit drawing', 'drawing_manager' => 'Drawing manager', 'insert_media' => 'Insert/edit media', diff --git a/resources/lang/uz/entities.php b/resources/lang/uz/entities.php index 7ca01e145..b747a7f67 100644 --- a/resources/lang/uz/entities.php +++ b/resources/lang/uz/entities.php @@ -42,10 +42,14 @@ return [ // Permissions and restrictions 'permissions' => 'Huquqlar', - 'permissions_intro' => 'Once enabled, These permissions will take priority over any set role permissions.', - 'permissions_enable' => 'Enable Custom Permissions', + 'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', 'permissions_save' => 'Save Permissions', 'permissions_owner' => 'Egasi', + 'permissions_role_everyone_else' => 'Everyone Else', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Override permissions for role', // Search 'search_results' => 'Qidiruv natijalari', diff --git a/resources/lang/vi/editor.php b/resources/lang/vi/editor.php index da7ffcf01..faf351da2 100644 --- a/resources/lang/vi/editor.php +++ b/resources/lang/vi/editor.php @@ -66,6 +66,7 @@ return [ 'insert_link_title' => 'Insert/Edit Link', 'insert_horizontal_line' => 'Insert horizontal line', 'insert_code_block' => 'Insert code block', + 'edit_code_block' => 'Edit code block', 'insert_drawing' => 'Insert/edit drawing', 'drawing_manager' => 'Drawing manager', 'insert_media' => 'Insert/edit media', diff --git a/resources/lang/vi/entities.php b/resources/lang/vi/entities.php index a8cc8b58e..32a4653c4 100644 --- a/resources/lang/vi/entities.php +++ b/resources/lang/vi/entities.php @@ -42,10 +42,14 @@ return [ // Permissions and restrictions 'permissions' => 'Quyền', - 'permissions_intro' => 'Một khi được bật, các quyền này sẽ được ưu tiên trên hết tất cả các quyền hạn khác.', - 'permissions_enable' => 'Bật quyền hạn tùy chỉnh', + 'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', 'permissions_save' => 'Lưu quyền hạn', 'permissions_owner' => 'Chủ sở hữu', + 'permissions_role_everyone_else' => 'Everyone Else', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Override permissions for role', // Search 'search_results' => 'Kết quả Tìm kiếm', diff --git a/resources/lang/zh_CN/editor.php b/resources/lang/zh_CN/editor.php index 14dc443f0..7d384b92e 100644 --- a/resources/lang/zh_CN/editor.php +++ b/resources/lang/zh_CN/editor.php @@ -66,6 +66,7 @@ return [ 'insert_link_title' => '插入/编辑链接', 'insert_horizontal_line' => '插入水平线', 'insert_code_block' => '插入代码块', + 'edit_code_block' => 'Edit code block', 'insert_drawing' => '插入/编辑绘图', 'drawing_manager' => '绘图管理器', 'insert_media' => '插入/编辑媒体', diff --git a/resources/lang/zh_CN/entities.php b/resources/lang/zh_CN/entities.php index 02b39f371..363b993cc 100644 --- a/resources/lang/zh_CN/entities.php +++ b/resources/lang/zh_CN/entities.php @@ -42,10 +42,14 @@ return [ // Permissions and restrictions 'permissions' => '权限', - 'permissions_intro' => '本设置优先于每个用户角色本身所具有的权限。', - 'permissions_enable' => '启用自定义权限', + 'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', 'permissions_save' => '保存权限', 'permissions_owner' => '拥有者', + 'permissions_role_everyone_else' => 'Everyone Else', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Override permissions for role', // Search 'search_results' => '搜索结果', diff --git a/resources/lang/zh_TW/activities.php b/resources/lang/zh_TW/activities.php index d2e6e06b1..a131f4a6c 100644 --- a/resources/lang/zh_TW/activities.php +++ b/resources/lang/zh_TW/activities.php @@ -28,8 +28,8 @@ return [ // Books 'book_create' => '已建立書本', 'book_create_notification' => '書本已建立成功', - 'book_create_from_chapter' => 'converted chapter to book', - 'book_create_from_chapter_notification' => 'Chapter successfully converted to a book', + 'book_create_from_chapter' => '將章節轉爲書籍', + 'book_create_from_chapter_notification' => '章節已轉換爲書籍', 'book_update' => '已更新書本', 'book_update_notification' => '書本已更新成功', 'book_delete' => '已刪除書本', @@ -38,14 +38,14 @@ return [ 'book_sort_notification' => '書本已重新排序成功', // Bookshelves - 'bookshelf_create' => 'created shelf', - 'bookshelf_create_notification' => 'Shelf successfully created', - 'bookshelf_create_from_book' => 'converted book to shelf', - 'bookshelf_create_from_book_notification' => 'Book successfully converted to a shelf', - 'bookshelf_update' => 'updated shelf', - 'bookshelf_update_notification' => 'Shelf successfully updated', - 'bookshelf_delete' => 'deleted shelf', - 'bookshelf_delete_notification' => 'Shelf successfully deleted', + 'bookshelf_create' => '已建立書棧', + 'bookshelf_create_notification' => '書棧已創建', + 'bookshelf_create_from_book' => '將書籍轉爲書棧', + 'bookshelf_create_from_book_notification' => '章節已轉爲書籍', + 'bookshelf_update' => '更新書棧', + 'bookshelf_update_notification' => '書棧已更新', + 'bookshelf_delete' => '刪除書棧', + 'bookshelf_delete_notification' => '書棧已刪除', // Favourites 'favourite_add_notification' => '":name" 已加入到你的最愛', diff --git a/resources/lang/zh_TW/editor.php b/resources/lang/zh_TW/editor.php index da7ffcf01..efefc15bc 100644 --- a/resources/lang/zh_TW/editor.php +++ b/resources/lang/zh_TW/editor.php @@ -7,47 +7,47 @@ */ return [ // General editor terms - 'general' => 'General', + 'general' => '通用', 'advanced' => 'Advanced', - 'none' => 'None', - 'cancel' => 'Cancel', - 'save' => 'Save', - 'close' => 'Close', - 'undo' => 'Undo', - 'redo' => 'Redo', - 'left' => 'Left', - 'center' => 'Center', - 'right' => 'Right', - 'top' => 'Top', - 'middle' => 'Middle', - 'bottom' => 'Bottom', - 'width' => 'Width', - 'height' => 'Height', - 'More' => 'More', - 'select' => 'Select...', + 'none' => '無', + 'cancel' => '取消', + 'save' => '保存', + 'close' => '關閉', + 'undo' => '復原', + 'redo' => '重做', + 'left' => '左側', + 'center' => '置中', + 'right' => '右側', + 'top' => '上方', + 'middle' => '中間', + 'bottom' => '底端', + 'width' => '寬度', + 'height' => '高度', + 'More' => '更多', + 'select' => '選擇...', // Toolbar - 'formats' => 'Formats', - 'header_large' => 'Large Header', - 'header_medium' => 'Medium Header', - 'header_small' => 'Small Header', + 'formats' => '格式', + 'header_large' => '大標題', + 'header_medium' => '中標題', + 'header_small' => '小標題', 'header_tiny' => 'Tiny Header', - 'paragraph' => 'Paragraph', - 'blockquote' => 'Blockquote', - 'inline_code' => 'Inline code', - 'callouts' => 'Callouts', - 'callout_information' => 'Information', - 'callout_success' => 'Success', - 'callout_warning' => 'Warning', - 'callout_danger' => 'Danger', - 'bold' => 'Bold', - 'italic' => 'Italic', - 'underline' => 'Underline', - 'strikethrough' => 'Strikethrough', - 'superscript' => 'Superscript', - 'subscript' => 'Subscript', - 'text_color' => 'Text color', - 'custom_color' => 'Custom color', + 'paragraph' => '段落', + 'blockquote' => '引用塊', + 'inline_code' => '行內程式碼', + 'callouts' => '圖說文字', + 'callout_information' => '資訊', + 'callout_success' => '成功', + 'callout_warning' => '警告', + 'callout_danger' => '危險', + 'bold' => '粗體', + 'italic' => '斜體', + 'underline' => '底線', + 'strikethrough' => '刪除線', + 'superscript' => '上標', + 'subscript' => '下標', + 'text_color' => '文本顏色', + 'custom_color' => '自訂顏色', 'remove_color' => 'Remove color', 'background_color' => 'Background color', 'align_left' => 'Align left', @@ -56,9 +56,9 @@ return [ 'align_justify' => 'Justify', 'list_bullet' => 'Bullet list', 'list_numbered' => 'Numbered list', - 'list_task' => 'Task list', - 'indent_increase' => 'Increase indent', - 'indent_decrease' => 'Decrease indent', + 'list_task' => '任務清單', + 'indent_increase' => '增加縮進', + 'indent_decrease' => '減少縮進', 'table' => 'Table', 'insert_image' => 'Insert image', 'insert_image_title' => 'Insert/Edit Image', @@ -66,6 +66,7 @@ return [ 'insert_link_title' => 'Insert/Edit Link', 'insert_horizontal_line' => 'Insert horizontal line', 'insert_code_block' => 'Insert code block', + 'edit_code_block' => 'Edit code block', 'insert_drawing' => 'Insert/edit drawing', 'drawing_manager' => 'Drawing manager', 'insert_media' => 'Insert/edit media', @@ -111,9 +112,9 @@ return [ 'paste_row_before' => 'Paste row before', 'paste_row_after' => 'Paste row after', 'row_type' => 'Row type', - 'row_type_header' => 'Header', - 'row_type_body' => 'Body', - 'row_type_footer' => 'Footer', + 'row_type_header' => '頁眉', + 'row_type_body' => '正文', + 'row_type_footer' => '页脚', 'alignment' => 'Alignment', 'cut_column' => 'Cut column', 'copy_column' => 'Copy column', @@ -136,11 +137,11 @@ return [ 'cell_border_hidden' => 'Hidden', // Images, links, details/summary & embed - 'source' => 'Source', + 'source' => '來源', 'alt_desc' => 'Alternative description', 'embed' => 'Embed', 'paste_embed' => 'Paste your embed code below:', - 'url' => 'URL', + 'url' => '網址', 'text_to_display' => 'Text to display', 'title' => 'Title', 'open_link' => 'Open link in...', @@ -148,7 +149,7 @@ return [ 'open_link_new' => 'New window', 'insert_collapsible' => 'Insert collapsible block', 'collapsible_unwrap' => 'Unwrap', - 'edit_label' => 'Edit label', + 'edit_label' => '編輯標記', 'toggle_open_closed' => 'Toggle open/closed', 'collapsible_edit' => 'Edit collapsible block', 'toggle_label' => 'Toggle label', diff --git a/resources/lang/zh_TW/entities.php b/resources/lang/zh_TW/entities.php index 574227287..a9a8c89c3 100644 --- a/resources/lang/zh_TW/entities.php +++ b/resources/lang/zh_TW/entities.php @@ -42,10 +42,14 @@ return [ // Permissions and restrictions 'permissions' => '權限', - 'permissions_intro' => '一旦啟用,這些權限將優先於任何設定的角色權限。', - 'permissions_enable' => '啟用自訂權限', + 'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.', + 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', + 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', 'permissions_save' => '儲存權限', 'permissions_owner' => '擁有者', + 'permissions_role_everyone_else' => 'Everyone Else', + 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', + 'permissions_role_override' => 'Override permissions for role', // Search 'search_results' => '搜尋結果', diff --git a/resources/lang/zh_TW/errors.php b/resources/lang/zh_TW/errors.php index c6644dd1c..b1735038d 100644 --- a/resources/lang/zh_TW/errors.php +++ b/resources/lang/zh_TW/errors.php @@ -58,7 +58,7 @@ return [ // Entities 'entity_not_found' => '找不到實體', - 'bookshelf_not_found' => 'Shelf not found', + 'bookshelf_not_found' => '未找到書棧', 'book_not_found' => '找不到書本', 'page_not_found' => '找不到頁面', 'chapter_not_found' => '找不到章節', diff --git a/resources/lang/zh_TW/settings.php b/resources/lang/zh_TW/settings.php index 1f523e760..cb6950835 100644 --- a/resources/lang/zh_TW/settings.php +++ b/resources/lang/zh_TW/settings.php @@ -10,8 +10,8 @@ return [ 'settings' => '設定', 'settings_save' => '儲存設定', 'settings_save_success' => '設定已儲存', - 'system_version' => 'System Version', - 'categories' => 'Categories', + 'system_version' => '系統版本', + 'categories' => '分類', // App Settings 'app_customization' => '自訂', @@ -27,8 +27,8 @@ return [ 'app_secure_images' => '更高安全性的圖片上傳', 'app_secure_images_toggle' => '啟用更高安全性的圖片上傳', 'app_secure_images_desc' => '因為效能因素,所有圖片都是公開的。此選項會在圖片的網址前加入一串隨機且難以猜測的字串。確保未啟用目錄索引,讓直接進入變得更困難。', - 'app_default_editor' => 'Default Page Editor', - 'app_default_editor_desc' => 'Select which editor will be used by default when editing new pages. This can be overridden at a page level where permissions allow.', + 'app_default_editor' => '預設頁面編輯器', + 'app_default_editor_desc' => '选择编辑新页面时默认使用的编辑器。这可以在权限允许的页面级别被覆盖。', 'app_custom_html' => '自訂 HTML 標題內容', 'app_custom_html_desc' => '此處加入的任何內容都將插入到每個頁面的 部分的底部,這對於覆蓋樣式或加入分析程式碼很方便。', 'app_custom_html_disabled_notice' => '在此設定頁面上停用了自訂 HTML 標題內容,以確保任何重大變更都能被還原。', @@ -156,7 +156,7 @@ return [ 'role_access_api' => '存取系統 API', 'role_manage_settings' => '管理應用程式設定', 'role_export_content' => '匯出內容', - 'role_editor_change' => 'Change page editor', + 'role_editor_change' => '重設頁面編輯器', 'role_asset' => '資源權限', 'roles_system_warning' => '請注意,有上述三項權限中的任一項的使用者都可以更改自己或系統中其他人的權限。有這些權限的角色只應分配給受信任的使用者。', 'role_asset_desc' => '對系統內資源的預設權限將由這裡的權限控制。若有單獨設定在書本、章節和頁面上的權限,將會覆寫這裡的權限設定。', @@ -250,7 +250,7 @@ return [ 'webhooks_events' => 'Webhook Events', 'webhooks_events_desc' => 'Select all the events that should trigger this webhook to be called.', 'webhooks_events_warning' => 'Keep in mind that these events will be triggered for all selected events, even if custom permissions are applied. Ensure that use of this webhook won\'t expose confidential content.', - 'webhooks_events_all' => 'All system events', + 'webhooks_events_all' => '全部系統活動', 'webhooks_name' => 'Webhook 名稱', 'webhooks_timeout' => 'Webhook Request Timeout (Seconds)', 'webhooks_endpoint' => 'Webhook Endpoint', From 905d339572137a93235bc757b36117142b5f2249 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 20 Oct 2022 12:25:02 +0100 Subject: [PATCH 52/55] Added greek language option --- app/Config/app.php | 2 +- app/Util/LanguageManager.php | 1 + resources/lang/en/settings.php | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/Config/app.php b/app/Config/app.php index 98f83fc39..90726c904 100644 --- a/app/Config/app.php +++ b/app/Config/app.php @@ -75,7 +75,7 @@ return [ 'locale' => env('APP_LANG', 'en'), // Locales available - 'locales' => ['en', 'ar', 'bg', 'bs', 'ca', 'cs', 'cy', 'da', 'de', 'de_informal', 'es', 'es_AR', 'et', 'eu', 'fa', 'fr', 'he', 'hr', 'hu', 'id', 'it', 'ja', 'ko', 'lt', 'lv', 'nl', 'nb', 'pt', 'pt_BR', 'sk', 'sl', 'sv', 'pl', 'ro', 'ru', 'tr', 'uk', 'uz', 'vi', 'zh_CN', 'zh_TW'], + 'locales' => ['en', 'ar', 'bg', 'bs', 'ca', 'cs', 'cy', 'da', 'de', 'de_informal', 'el', 'es', 'es_AR', 'et', 'eu', 'fa', 'fr', 'he', 'hr', 'hu', 'id', 'it', 'ja', 'ko', 'lt', 'lv', 'nl', 'nb', 'pt', 'pt_BR', 'sk', 'sl', 'sv', 'pl', 'ro', 'ru', 'tr', 'uk', 'uz', 'vi', 'zh_CN', 'zh_TW'], // Application Fallback Locale 'fallback_locale' => 'en', diff --git a/app/Util/LanguageManager.php b/app/Util/LanguageManager.php index 201bbda3d..ed68f647c 100644 --- a/app/Util/LanguageManager.php +++ b/app/Util/LanguageManager.php @@ -28,6 +28,7 @@ class LanguageManager 'de' => ['iso' => 'de_DE', 'windows' => 'German'], 'de_informal' => ['iso' => 'de_DE', 'windows' => 'German'], 'en' => ['iso' => 'en_GB', 'windows' => 'English'], + 'el' => ['iso' => 'el_GR', 'windows' => 'Greek'], 'es' => ['iso' => 'es_ES', 'windows' => 'Spanish'], 'es_AR' => ['iso' => 'es_AR', 'windows' => 'Spanish'], 'et' => ['iso' => 'et_EE', 'windows' => 'Estonian'], diff --git a/resources/lang/en/settings.php b/resources/lang/en/settings.php index e2a22ee37..1ad271e7c 100755 --- a/resources/lang/en/settings.php +++ b/resources/lang/en/settings.php @@ -280,6 +280,7 @@ return [ 'da' => 'Dansk', 'de' => 'Deutsch (Sie)', 'de_informal' => 'Deutsch (Du)', + 'el' => 'ελληνικά', 'es' => 'Español', 'es_AR' => 'Español Argentina', 'et' => 'Eesti keel', From 3f61bfc43caf49fb3c61712e8a1581c2dc199c0e Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 21 Oct 2022 10:13:11 +0100 Subject: [PATCH 53/55] Fixed toggle controls on added content permission role rows --- resources/js/components/entity-permissions.js | 7 +++---- resources/js/services/dom.js | 13 +++++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/resources/js/components/entity-permissions.js b/resources/js/components/entity-permissions.js index 917dcc72d..c67c85f19 100644 --- a/resources/js/components/entity-permissions.js +++ b/resources/js/components/entity-permissions.js @@ -1,6 +1,8 @@ /** * @extends {Component} */ +import {htmlToDom} from "../services/dom"; + class EntityPermissions { setup() { @@ -53,11 +55,8 @@ class EntityPermissions { // Get and insert new row const resp = await window.$http.get(`/permissions/form-row/${this.entityType}/${roleId}`); - const wrap = document.createElement('div'); - wrap.innerHTML = resp.data; - const row = wrap.children[0]; + const row = htmlToDom(resp.data); this.roleContainer.append(row); - window.components.init(row); this.roleSelect.disabled = false; } diff --git a/resources/js/services/dom.js b/resources/js/services/dom.js index 7a7b2c9bc..eb5f6a853 100644 --- a/resources/js/services/dom.js +++ b/resources/js/services/dom.js @@ -117,4 +117,17 @@ export function removeLoading(element) { for (const el of loadingEls) { el.remove(); } +} + +/** + * Convert the given html data into a live DOM element. + * Initiates any components defined in the data. + * @param {String} html + * @returns {Element} + */ +export function htmlToDom(html) { + const wrap = document.createElement('div'); + wrap.innerHTML = html; + window.components.init(wrap); + return wrap.children[0]; } \ No newline at end of file From 7b2fd515dabd8817646fcc23a47790ca3fcc33ab Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 21 Oct 2022 10:41:55 +0100 Subject: [PATCH 54/55] Updated test to align with latest translation --- tests/Auth/UserInviteTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Auth/UserInviteTest.php b/tests/Auth/UserInviteTest.php index 38124cc1a..ccbb538a6 100644 --- a/tests/Auth/UserInviteTest.php +++ b/tests/Auth/UserInviteTest.php @@ -54,7 +54,7 @@ class UserInviteTest extends TestCase /** @var MailMessage $mail */ $mail = $notification->toMail($notifiable); - return 'Du wurdest eingeladen BookStack beizutreten!' === $mail->subject && + return 'Sie wurden eingeladen BookStack beizutreten!' === $mail->subject && 'Ein Konto wurde für Sie auf BookStack erstellt.' === $mail->greeting; }); } From 103649887fe31cf0620221ed57997502abee1077 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 21 Oct 2022 11:15:35 +0100 Subject: [PATCH 55/55] Updated translator attribution before release v22.10 --- .github/translators.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/translators.txt b/.github/translators.txt index d7db20fc4..ec38b3342 100644 --- a/.github/translators.txt +++ b/.github/translators.txt @@ -280,3 +280,11 @@ DerLinkman (derlinkman) :: German; German Informal TurnArabic :: Arabic Martin Sebek (sebekmartin) :: Czech Kuchinashi Hoshikawa (kuchinashi) :: Chinese Simplified +digilady :: Greek +Linus (LinusOP) :: Swedish +Felipe Cardoso (felipecardosoruff) :: Portuguese, Brazilian +RandomUser0815 :: German +Ismael Mesquita (mesquitoliveira) :: Portuguese, Brazilian +구인회 (laskdjlaskdj12) :: Korean +LiZerui (CNLiZerui) :: Chinese Traditional +Fabrice Boyer (FabriceBoyer) :: French
+ {{ trans('auth.name') }} / {{ trans('auth.email') }} {{ trans('settings.role_user_roles') }} + {{ trans('settings.role_user_roles') }} {{ trans('settings.users_latest_activity') }}