MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
-MAIL_ENCRYPTION=null
\ No newline at end of file
+MAIL_ENCRYPTION=null
# overrides can be made. Defaults to disabled.
APP_THEME=false
+# Trusted Proxies
+# Used to indicate trust of systems that proxy to the application so
+# certain header values (Such as "X-Forwarded-For") can be used from the
+# incoming proxy request to provide origin detail.
+# Set to an IP address, or multiple comma seperated IP addresses.
+# Can alternatively be set to "*" to trust all proxy addresses.
+APP_PROXIES=null
+
# Database details
# Host can contain a port (localhost:3306) or a separate DB_PORT option can be used.
DB_HOST=localhost
# Contents of the robots.txt file can be overridden, making this option obsolete.
ALLOW_ROBOTS=null
+# Allow server-side fetches to be performed to potentially unknown
+# and user-provided locations. Primarily used in exports when loading
+# in externally referenced assets.
+# Can be 'true' or 'false'.
+ALLOW_UNTRUSTED_SERVER_FETCHING=false
+
# A list of hosts that BookStack can be iframed within.
# Space separated if multiple. BookStack host domain is auto-inferred.
# For Example: ALLOWED_IFRAME_HOSTS="https://example.com https://a.example.com"
Rem (remkovdhoef) :: Dutch
syn7ax69 :: Bulgarian; Turkish
Blaade :: French
+Behzad HosseinPoor (behzad.hp) :: Persian
+Ole Aldric (Swoy) :: Norwegian Bokmal
+fharis arabia (raednahdi) :: Arabic
+Alexander Predl (Harveyhase68) :: German
+Rem (Rem9000) :: Dutch
+Michał Stelmach (stelmach-web) :: Polish
+arniom :: French
+REMOVED_USER :: Turkish
+林祖年 (contagion) :: Chinese Traditional
+Siamak Guodarzi (siamakgoudarzi88) :: Persian
+Lis Maestrelo (lismtrl) :: Portuguese, Brazilian
+Nathanaël (nathanaelhoun) :: French
+A Ibnu Hibban (abd.ibnuhibban) :: Indonesian
+Frost-ZX :: Chinese Simplified
+Kuzma Simonov (ovmach) :: Russian
+Vojtěch Krystek (acantophis) :: Czech
+Michał Lipok (mLipok) :: Polish
+Nicolas Pawlak (Mikolajek) :: French; Polish; German
+Thomas Hansen (thomasdk81) :: Danish
+Hl2run :: Slovak
+Ngo Tri Hoai (trihoai) :: Vietnamese
+Atalonica :: Catalan
+慕容潭谈 (591442386) :: Chinese Simplified
*/
protected function newActivityForUser(string $type): Activity
{
+ $ip = request()->ip() ?? '';
+
return $this->activity->newInstance()->forceFill([
'type' => strtolower($type),
'user_id' => user()->id,
+ 'ip' => config('app.env') === 'demo' ? '127.0.0.1' : $ip,
]);
}
const AUTH_PASSWORD_RESET_UPDATE = 'auth_password_reset_update';
const AUTH_LOGIN = 'auth_login';
const AUTH_REGISTER = 'auth_register';
+
+ const MFA_SETUP_METHOD = 'mfa_setup_method';
+ const MFA_REMOVE_METHOD = 'mfa_remove_method';
}
use Illuminate\Database\Eloquent\Relations\MorphTo;
/**
- * @property string text
- * @property string html
- * @property int|null parent_id
- * @property int local_id
+ * @property int $id
+ * @property string $text
+ * @property string $html
+ * @property int|null $parent_id
+ * @property int $local_id
*/
class Comment extends Model
{
use BookStack\Auth\Permissions\PermissionService;
use BookStack\Entities\Models\Entity;
-use DB;
use Illuminate\Support\Collection;
+use Illuminate\Support\Facades\DB;
class TagRepo
{
namespace BookStack\Api;
+use BookStack\Auth\Access\LoginService;
use BookStack\Exceptions\ApiAuthException;
use Illuminate\Auth\GuardHelpers;
use Illuminate\Contracts\Auth\Authenticatable;
*/
protected $request;
+ /**
+ * @var LoginService
+ */
+ protected $loginService;
+
/**
* The last auth exception thrown in this request.
*
/**
* ApiTokenGuard constructor.
*/
- public function __construct(Request $request)
+ public function __construct(Request $request, LoginService $loginService)
{
$this->request = $request;
+ $this->loginService = $loginService;
}
/**
$this->validateToken($token, $secret);
+ if ($this->loginService->awaitingEmailConfirmation($token->user)) {
+ throw new ApiAuthException(trans('errors.email_confirmation_awaiting'));
+ }
+
return $token->user;
}
* Create new confirmation for a user,
* Also removes any existing old ones.
*
- * @param User $user
- *
* @throws ConfirmationEmailException
*/
public function sendConfirmation(User $user)
/**
* Check if confirmation is required in this instance.
- *
- * @return bool
*/
public function confirmationRequired(): bool
{
*/
public function loginUsingId($id, $remember = false)
{
- if (!is_null($user = $this->provider->retrieveById($id))) {
- $this->login($user, $remember);
-
- return $user;
- }
-
+ // Always return false as to disable this method,
+ // Logins should route through LoginService.
return false;
}
--- /dev/null
+<?php
+
+namespace BookStack\Auth\Access;
+
+use BookStack\Actions\ActivityType;
+use BookStack\Auth\Access\Mfa\MfaSession;
+use BookStack\Auth\User;
+use BookStack\Exceptions\StoppedAuthenticationException;
+use BookStack\Facades\Activity;
+use BookStack\Facades\Theme;
+use BookStack\Theming\ThemeEvents;
+use Exception;
+
+class LoginService
+{
+ protected const LAST_LOGIN_ATTEMPTED_SESSION_KEY = 'auth-login-last-attempted';
+
+ protected $mfaSession;
+ protected $emailConfirmationService;
+
+ public function __construct(MfaSession $mfaSession, EmailConfirmationService $emailConfirmationService)
+ {
+ $this->mfaSession = $mfaSession;
+ $this->emailConfirmationService = $emailConfirmationService;
+ }
+
+ /**
+ * Log the given user into the system.
+ * Will start a login of the given user but will prevent if there's
+ * a reason to (MFA or Unconfirmed Email).
+ * Returns a boolean to indicate the current login result.
+ *
+ * @throws StoppedAuthenticationException
+ */
+ public function login(User $user, string $method, bool $remember = false): void
+ {
+ if ($this->awaitingEmailConfirmation($user) || $this->needsMfaVerification($user)) {
+ $this->setLastLoginAttemptedForUser($user, $method, $remember);
+
+ throw new StoppedAuthenticationException($user, $this);
+ }
+
+ $this->clearLastLoginAttempted();
+ auth()->login($user, $remember);
+ Activity::add(ActivityType::AUTH_LOGIN, "{$method}; {$user->logDescriptor()}");
+ Theme::dispatch(ThemeEvents::AUTH_LOGIN, $method, $user);
+
+ // Authenticate on all session guards if a likely admin
+ if ($user->can('users-manage') && $user->can('user-roles-manage')) {
+ $guards = ['standard', 'ldap', 'saml2'];
+ foreach ($guards as $guard) {
+ auth($guard)->login($user);
+ }
+ }
+ }
+
+ /**
+ * Reattempt a system login after a previous stopped attempt.
+ *
+ * @throws Exception
+ */
+ public function reattemptLoginFor(User $user)
+ {
+ if ($user->id !== ($this->getLastLoginAttemptUser()->id ?? null)) {
+ throw new Exception('Login reattempt user does align with current session state');
+ }
+
+ $lastLoginDetails = $this->getLastLoginAttemptDetails();
+ $this->login($user, $lastLoginDetails['method'], $lastLoginDetails['remember'] ?? false);
+ }
+
+ /**
+ * Get the last user that was attempted to be logged in.
+ * Only exists if the last login attempt had correct credentials
+ * but had been prevented by a secondary factor.
+ */
+ public function getLastLoginAttemptUser(): ?User
+ {
+ $id = $this->getLastLoginAttemptDetails()['user_id'];
+
+ return User::query()->where('id', '=', $id)->first();
+ }
+
+ /**
+ * Get the details of the last login attempt.
+ * Checks upon a ttl of about 1 hour since that last attempted login.
+ *
+ * @return array{user_id: ?string, method: ?string, remember: bool}
+ */
+ protected function getLastLoginAttemptDetails(): array
+ {
+ $value = session()->get(self::LAST_LOGIN_ATTEMPTED_SESSION_KEY);
+ if (!$value) {
+ return ['user_id' => null, 'method' => null];
+ }
+
+ [$id, $method, $remember, $time] = explode(':', $value);
+ $hourAgo = time() - (60 * 60);
+ if ($time < $hourAgo) {
+ $this->clearLastLoginAttempted();
+
+ return ['user_id' => null, 'method' => null];
+ }
+
+ return ['user_id' => $id, 'method' => $method, 'remember' => boolval($remember)];
+ }
+
+ /**
+ * Set the last login attempted user.
+ * Must be only used when credentials are correct and a login could be
+ * achieved but a secondary factor has stopped the login.
+ */
+ protected function setLastLoginAttemptedForUser(User $user, string $method, bool $remember)
+ {
+ session()->put(
+ self::LAST_LOGIN_ATTEMPTED_SESSION_KEY,
+ implode(':', [$user->id, $method, $remember, time()])
+ );
+ }
+
+ /**
+ * Clear the last login attempted session value.
+ */
+ protected function clearLastLoginAttempted(): void
+ {
+ session()->remove(self::LAST_LOGIN_ATTEMPTED_SESSION_KEY);
+ }
+
+ /**
+ * Check if MFA verification is needed.
+ */
+ public function needsMfaVerification(User $user): bool
+ {
+ return !$this->mfaSession->isVerifiedForUser($user) && $this->mfaSession->isRequiredForUser($user);
+ }
+
+ /**
+ * Check if the given user is awaiting email confirmation.
+ */
+ public function awaitingEmailConfirmation(User $user): bool
+ {
+ return $this->emailConfirmationService->confirmationRequired() && !$user->email_confirmed;
+ }
+
+ /**
+ * Attempt the login of a user using the given credentials.
+ * Meant to mirror Laravel's default guard 'attempt' method
+ * but in a manner that always routes through our login system.
+ * May interrupt the flow if extra authentication requirements are imposed.
+ *
+ * @throws StoppedAuthenticationException
+ */
+ public function attempt(array $credentials, string $method, bool $remember = false): bool
+ {
+ $result = auth()->attempt($credentials, $remember);
+ if ($result) {
+ $user = auth()->user();
+ auth()->logout();
+ $this->login($user, $method, $remember);
+ }
+
+ return $result;
+ }
+}
--- /dev/null
+<?php
+
+namespace BookStack\Auth\Access\Mfa;
+
+use Illuminate\Support\Str;
+
+class BackupCodeService
+{
+ /**
+ * Generate a new set of 16 backup codes.
+ */
+ public function generateNewSet(): array
+ {
+ $codes = [];
+ while (count($codes) < 16) {
+ $code = Str::random(5) . '-' . Str::random(5);
+ if (!in_array($code, $codes)) {
+ $codes[] = strtolower($code);
+ }
+ }
+
+ return $codes;
+ }
+
+ /**
+ * Check if the given code matches one of the available options.
+ */
+ public function inputCodeExistsInSet(string $code, string $codeSet): bool
+ {
+ $cleanCode = $this->cleanInputCode($code);
+ $codes = json_decode($codeSet);
+
+ return in_array($cleanCode, $codes);
+ }
+
+ /**
+ * Remove the given input code from the given available options.
+ * Will return a JSON string containing the codes.
+ */
+ public function removeInputCodeFromSet(string $code, string $codeSet): string
+ {
+ $cleanCode = $this->cleanInputCode($code);
+ $codes = json_decode($codeSet);
+ $pos = array_search($cleanCode, $codes, true);
+ array_splice($codes, $pos, 1);
+
+ return json_encode($codes);
+ }
+
+ /**
+ * Count the number of codes in the given set.
+ */
+ public function countCodesInSet(string $codeSet): int
+ {
+ return count(json_decode($codeSet));
+ }
+
+ protected function cleanInputCode(string $code): string
+ {
+ return strtolower(str_replace(' ', '-', trim($code)));
+ }
+}
--- /dev/null
+<?php
+
+namespace BookStack\Auth\Access\Mfa;
+
+use BookStack\Auth\User;
+
+class MfaSession
+{
+ /**
+ * Check if MFA is required for the given user.
+ */
+ public function isRequiredForUser(User $user): bool
+ {
+ // TODO - Test both these cases
+ return $user->mfaValues()->exists() || $this->userRoleEnforcesMfa($user);
+ }
+
+ /**
+ * Check if the given user is pending MFA setup.
+ * (MFA required but not yet configured).
+ */
+ public function isPendingMfaSetup(User $user): bool
+ {
+ return $this->isRequiredForUser($user) && !$user->mfaValues()->exists();
+ }
+
+ /**
+ * Check if a role of the given user enforces MFA.
+ */
+ protected function userRoleEnforcesMfa(User $user): bool
+ {
+ return $user->roles()
+ ->where('mfa_enforced', '=', true)
+ ->exists();
+ }
+
+ /**
+ * Check if the current MFA session has already been verified for the given user.
+ */
+ public function isVerifiedForUser(User $user): bool
+ {
+ return session()->get($this->getMfaVerifiedSessionKey($user)) === 'true';
+ }
+
+ /**
+ * Mark the current session as MFA-verified.
+ */
+ public function markVerifiedForUser(User $user): void
+ {
+ session()->put($this->getMfaVerifiedSessionKey($user), 'true');
+ }
+
+ /**
+ * Get the session key in which the MFA verification status is stored.
+ */
+ protected function getMfaVerifiedSessionKey(User $user): string
+ {
+ return 'mfa-verification-passed:' . $user->id;
+ }
+}
--- /dev/null
+<?php
+
+namespace BookStack\Auth\Access\Mfa;
+
+use BookStack\Auth\User;
+use Carbon\Carbon;
+use Illuminate\Database\Eloquent\Model;
+
+/**
+ * @property int $id
+ * @property int $user_id
+ * @property string $method
+ * @property string $value
+ * @property Carbon $created_at
+ * @property Carbon $updated_at
+ */
+class MfaValue extends Model
+{
+ protected static $unguarded = true;
+
+ const METHOD_TOTP = 'totp';
+ const METHOD_BACKUP_CODES = 'backup_codes';
+
+ /**
+ * Get all the MFA methods available.
+ */
+ public static function allMethods(): array
+ {
+ return [self::METHOD_TOTP, self::METHOD_BACKUP_CODES];
+ }
+
+ /**
+ * Upsert a new MFA value for the given user and method
+ * using the provided value.
+ */
+ public static function upsertWithValue(User $user, string $method, string $value): void
+ {
+ /** @var MfaValue $mfaVal */
+ $mfaVal = static::query()->firstOrNew([
+ 'user_id' => $user->id,
+ 'method' => $method,
+ ]);
+ $mfaVal->setValue($value);
+ $mfaVal->save();
+ }
+
+ /**
+ * Easily get the decrypted MFA value for the given user and method.
+ */
+ public static function getValueForUser(User $user, string $method): ?string
+ {
+ /** @var MfaValue $mfaVal */
+ $mfaVal = static::query()
+ ->where('user_id', '=', $user->id)
+ ->where('method', '=', $method)
+ ->first();
+
+ return $mfaVal ? $mfaVal->getValue() : null;
+ }
+
+ /**
+ * Decrypt the value attribute upon access.
+ */
+ protected function getValue(): string
+ {
+ return decrypt($this->value);
+ }
+
+ /**
+ * Encrypt the value attribute upon access.
+ */
+ protected function setValue($value): void
+ {
+ $this->value = encrypt($value);
+ }
+}
--- /dev/null
+<?php
+
+namespace BookStack\Auth\Access\Mfa;
+
+use BaconQrCode\Renderer\Color\Rgb;
+use BaconQrCode\Renderer\Image\SvgImageBackEnd;
+use BaconQrCode\Renderer\ImageRenderer;
+use BaconQrCode\Renderer\RendererStyle\Fill;
+use BaconQrCode\Renderer\RendererStyle\RendererStyle;
+use BaconQrCode\Writer;
+use PragmaRX\Google2FA\Google2FA;
+use PragmaRX\Google2FA\Support\Constants;
+
+class TotpService
+{
+ protected $google2fa;
+
+ public function __construct(Google2FA $google2fa)
+ {
+ $this->google2fa = $google2fa;
+ // Use SHA1 as a default, Personal testing of other options in 2021 found
+ // many apps lack support for other algorithms yet still will scan
+ // the code causing a confusing UX.
+ $this->google2fa->setAlgorithm(Constants::SHA1);
+ }
+
+ /**
+ * Generate a new totp secret key.
+ */
+ public function generateSecret(): string
+ {
+ /** @noinspection PhpUnhandledExceptionInspection */
+ return $this->google2fa->generateSecretKey();
+ }
+
+ /**
+ * Generate a TOTP URL from secret key.
+ */
+ public function generateUrl(string $secret): string
+ {
+ return $this->google2fa->getQRCodeUrl(
+ setting('app-name'),
+ user()->email,
+ $secret
+ );
+ }
+
+ /**
+ * Generate a QR code to display a TOTP URL.
+ */
+ public function generateQrCodeSvg(string $url): string
+ {
+ $color = Fill::uniformColor(new Rgb(255, 255, 255), new Rgb(32, 110, 167));
+
+ return (new Writer(
+ new ImageRenderer(
+ new RendererStyle(192, 4, null, null, $color),
+ new SvgImageBackEnd()
+ )
+ ))->writeString($url);
+ }
+
+ /**
+ * Verify that the user provided code is valid for the secret.
+ * The secret must be known, not user-provided.
+ */
+ public function verifyCode(string $code, string $secret): bool
+ {
+ /** @noinspection PhpUnhandledExceptionInspection */
+ return $this->google2fa->verifyKey($secret, $code);
+ }
+}
--- /dev/null
+<?php
+
+namespace BookStack\Auth\Access\Mfa;
+
+use Illuminate\Contracts\Validation\Rule;
+
+class TotpValidationRule implements Rule
+{
+ protected $secret;
+ protected $totpService;
+
+ /**
+ * Create a new rule instance.
+ * Takes the TOTP secret that must be system provided, not user provided.
+ */
+ public function __construct(string $secret)
+ {
+ $this->secret = $secret;
+ $this->totpService = app()->make(TotpService::class);
+ }
+
+ /**
+ * Determine if the validation rule passes.
+ */
+ public function passes($attribute, $value)
+ {
+ return $this->totpService->verifyCode($value, $this->secret);
+ }
+
+ /**
+ * Get the validation error message.
+ */
+ public function message()
+ {
+ return trans('validation.totp');
+ }
+}
namespace BookStack\Auth\Access;
-use BookStack\Actions\ActivityType;
use BookStack\Auth\User;
use BookStack\Exceptions\JsonDebugException;
use BookStack\Exceptions\SamlException;
+use BookStack\Exceptions\StoppedAuthenticationException;
use BookStack\Exceptions\UserRegistrationException;
-use BookStack\Facades\Activity;
-use BookStack\Facades\Theme;
-use BookStack\Theming\ThemeEvents;
use Exception;
use Illuminate\Support\Str;
use OneLogin\Saml2\Auth;
{
protected $config;
protected $registrationService;
- protected $user;
+ protected $loginService;
/**
* Saml2Service constructor.
*/
- public function __construct(RegistrationService $registrationService, User $user)
+ public function __construct(RegistrationService $registrationService, LoginService $loginService)
{
$this->config = config('saml2');
$this->registrationService = $registrationService;
- $this->user = $user;
+ $this->loginService = $loginService;
}
/**
*/
protected function getOrRegisterUser(array $userDetails): ?User
{
- $user = $this->user->newQuery()
+ $user = User::query()
->where('external_auth_id', '=', $userDetails['external_id'])
->first();
* @throws SamlException
* @throws JsonDebugException
* @throws UserRegistrationException
+ * @throws StoppedAuthenticationException
*/
public function processLoginCallback(string $samlID, array $samlAttributes): User
{
$this->syncWithGroups($user, $groups);
}
- auth()->login($user);
- Activity::add(ActivityType::AUTH_LOGIN, "saml2; {$user->logDescriptor()}");
- Theme::dispatch(ThemeEvents::AUTH_LOGIN, 'saml2', $user);
+ $this->loginService->login($user, 'saml2');
return $user;
}
namespace BookStack\Auth\Access;
-use BookStack\Actions\ActivityType;
use BookStack\Auth\SocialAccount;
use BookStack\Auth\User;
use BookStack\Exceptions\SocialDriverNotConfigured;
use BookStack\Exceptions\SocialSignInAccountNotUsed;
use BookStack\Exceptions\UserRegistrationException;
-use BookStack\Facades\Activity;
-use BookStack\Facades\Theme;
-use BookStack\Theming\ThemeEvents;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Str;
use Laravel\Socialite\Contracts\Factory as Socialite;
*/
protected $socialite;
+ /**
+ * @var LoginService
+ */
+ protected $loginService;
+
/**
* The default built-in social drivers we support.
*
/**
* SocialAuthService constructor.
*/
- public function __construct(Socialite $socialite)
+ public function __construct(Socialite $socialite, LoginService $loginService)
{
$this->socialite = $socialite;
+ $this->loginService = $loginService;
}
/**
// When a user is not logged in and a matching SocialAccount exists,
// Simply log the user into the application.
if (!$isLoggedIn && $socialAccount !== null) {
- auth()->login($socialAccount->user);
- Activity::add(ActivityType::AUTH_LOGIN, $socialAccount);
- Theme::dispatch(ThemeEvents::AUTH_LOGIN, $socialDriver, $socialAccount->user);
+ $this->loginService->login($socialAccount->user, $socialDriver);
return redirect()->intended('/');
}
use BookStack\Exceptions\UserTokenExpiredException;
use BookStack\Exceptions\UserTokenNotFoundException;
use Carbon\Carbon;
-use Illuminate\Database\Connection as Database;
+use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use stdClass;
*/
protected $expiryTime = 24;
- protected $db;
-
- /**
- * UserTokenService constructor.
- *
- * @param Database $db
- */
- public function __construct(Database $db)
- {
- $this->db = $db;
- }
-
/**
* Delete all email confirmations that belong to a user.
*
*/
public function deleteByUser(User $user)
{
- return $this->db->table($this->tokenTable)
+ return DB::table($this->tokenTable)
->where('user_id', '=', $user->id)
->delete();
}
protected function createTokenForUser(User $user): string
{
$token = $this->generateToken();
- $this->db->table($this->tokenTable)->insert([
+ DB::table($this->tokenTable)->insert([
'user_id' => $user->id,
'token' => $token,
'created_at' => Carbon::now(),
*/
protected function tokenExists(string $token): bool
{
- return $this->db->table($this->tokenTable)
+ return DB::table($this->tokenTable)
->where('token', '=', $token)->exists();
}
*/
protected function getEntryByToken(string $token)
{
- return $this->db->table($this->tokenTable)
+ return DB::table($this->tokenTable)
->where('token', '=', $token)
->first();
}
/**
* Filter items that have entities set as a polymorphic relation.
*
- * @param Builder|\Illuminate\Database\Query\Builder $query
+ * @param Builder|QueryBuilder $query
*/
public function filterRestrictedEntityRelations($query, string $tableName, string $entityIdColumn, string $entityTypeColumn, string $action = 'view')
{
$q = $query->where(function ($query) use ($tableDetails, $action) {
$query->whereExists(function ($permissionQuery) use (&$tableDetails, $action) {
+ /** @var Builder $permissionQuery */
$permissionQuery->select(['role_id'])->from('joint_permissions')
- ->whereRaw('joint_permissions.entity_id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
- ->whereRaw('joint_permissions.entity_type=' . $tableDetails['tableName'] . '.' . $tableDetails['entityTypeColumn'])
+ ->whereColumn('joint_permissions.entity_id', '=', $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
+ ->whereColumn('joint_permissions.entity_type', '=', $tableDetails['tableName'] . '.' . $tableDetails['entityTypeColumn'])
->where('action', '=', $action)
->whereIn('role_id', $this->getCurrentUserRoles())
->where(function (QueryBuilder $query) {
$q = $query->where(function ($query) use ($tableDetails, $morphClass) {
$query->where(function ($query) use (&$tableDetails, $morphClass) {
$query->whereExists(function ($permissionQuery) use (&$tableDetails, $morphClass) {
+ /** @var Builder $permissionQuery */
$permissionQuery->select('id')->from('joint_permissions')
- ->whereRaw('joint_permissions.entity_id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
+ ->whereColumn('joint_permissions.entity_id', '=', $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
->where('entity_type', '=', $morphClass)
->where('action', '=', 'view')
->whereIn('role_id', $this->getCurrentUserRoles())
public function saveNewRole(array $roleData): Role
{
$role = $this->role->newInstance($roleData);
+ $role->mfa_enforced = ($roleData['mfa_enforced'] ?? 'false') === 'true';
$role->save();
$permissions = isset($roleData['permissions']) ? array_keys($roleData['permissions']) : [];
$this->assignRolePermissions($role, $permissions);
$role->fill($roleData);
+ $role->mfa_enforced = ($roleData['mfa_enforced'] ?? 'false') === 'true';
$role->save();
$this->permissionService->buildJointPermissionForRole($role);
Activity::add(ActivityType::ROLE_UPDATE, $role);
/**
* Class Role.
*
- * @property int $id
- * @property string $display_name
- * @property string $description
- * @property string $external_auth_id
- * @property string $system_name
+ * @property int $id
+ * @property string $display_name
+ * @property string $description
+ * @property string $external_auth_id
+ * @property string $system_name
+ * @property bool $mfa_enforced
+ * @property Collection $users
*/
class Role extends Model implements Loggable
{
use BookStack\Actions\Favourite;
use BookStack\Api\ApiToken;
+use BookStack\Auth\Access\Mfa\MfaValue;
use BookStack\Entities\Tools\SlugGenerator;
use BookStack\Interfaces\Loggable;
use BookStack\Interfaces\Sluggable;
* @property string $external_auth_id
* @property string $system_name
* @property Collection $roles
+ * @property Collection $mfaValues
*/
class User extends Model implements AuthenticatableContract, CanResetPasswordContract, Loggable, Sluggable
{
return $this->hasMany(Favourite::class);
}
+ /**
+ * Get the MFA values belonging to this use.
+ */
+ public function mfaValues(): HasMany
+ {
+ return $this->hasMany(MfaValue::class);
+ }
+
/**
* Get the last activity time for this user.
*/
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Pagination\LengthAwarePaginator;
-use Log;
+use Illuminate\Support\Facades\Log;
class UserRepo
{
$query = User::query()->select(['*'])
->withLastActivityAt()
->with(['roles', 'avatar'])
+ ->withCount('mfaValues')
->orderBy($sort, $sortData['order']);
if ($sortData['search']) {
$user->socialAccounts()->delete();
$user->apiTokens()->delete();
$user->favourites()->delete();
+ $user->mfaValues()->delete();
$user->delete();
// Delete user profile images
// Even when overridden the WYSIWYG editor may still escape script content.
'allow_content_scripts' => env('ALLOW_CONTENT_SCRIPTS', false),
+ // Allow server-side fetches to be performed to potentially unknown
+ // and user-provided locations. Primarily used in exports when loading
+ // in externally referenced assets.
+ 'allow_untrusted_server_fetching' => env('ALLOW_UNTRUSTED_SERVER_FETCHING', false),
+
// Override the default behaviour for allowing crawlers to crawl the instance.
// May be ignored if view has be overridden or modified.
// Defaults to null since, if not set, 'app-public' status used instead.
'locale' => env('APP_LANG', 'en'),
// Locales available
- 'locales' => ['en', 'ar', 'bg', 'bs', 'ca', 'cs', 'da', 'de', 'de_informal', 'es', 'es_AR', 'fa', 'fr', 'he', 'hr', 'hu', 'id', 'it', 'ja', 'ko', 'lv', 'nl', 'nb', 'pt', 'pt_BR', 'sk', 'sl', 'sv', 'pl', 'ru', 'th', 'tr', 'uk', 'vi', 'zh_CN', 'zh_TW'],
+ 'locales' => ['en', 'ar', 'bg', 'bs', 'ca', 'cs', 'da', 'de', 'de_informal', 'es', 'es_AR', 'fa', 'fr', 'he', 'hr', 'hu', 'id', 'it', 'ja', 'ko', 'lt', 'lv', 'nl', 'nb', 'pt', 'pt_BR', 'sk', 'sl', 'sv', 'pl', 'ru', 'th', 'tr', 'uk', 'vi', 'zh_CN', 'zh_TW'],
// Application Fallback Locale
'fallback_locale' => 'en',
'port' => $mysql_port,
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
- 'prefix' => '',
+ // Prefixes are only semi-supported and may be unstable
+ // since they are not tested as part of our automated test suite.
+ // If used, the prefix should not be changed otherwise you will likely receive errors.
+ 'prefix' => env('DB_TABLE_PREFIX', ''),
'prefix_indexes' => true,
'strict' => false,
'engine' => null,
* Times-Roman, Times-Bold, Times-BoldItalic, Times-Italic,
* Symbol, ZapfDingbats.
*/
- 'DOMPDF_FONT_DIR' => storage_path('fonts/'), // advised by dompdf (https://github.com/dompdf/dompdf/pull/782)
+ 'font_dir' => storage_path('fonts/'), // advised by dompdf (https://github.com/dompdf/dompdf/pull/782)
/**
* The location of the DOMPDF font cache directory.
*
* Note: This directory must exist and be writable by the webserver process.
*/
- 'DOMPDF_FONT_CACHE' => storage_path('fonts/'),
+ 'font_cache' => storage_path('fonts/'),
/**
* The location of a temporary directory.
* The temporary directory is required to download remote images and when
* using the PFDLib back end.
*/
- 'DOMPDF_TEMP_DIR' => sys_get_temp_dir(),
+ 'temp_dir' => sys_get_temp_dir(),
/**
* ==== IMPORTANT ====.
* direct class use like:
* $dompdf = new DOMPDF(); $dompdf->load_html($htmldata); $dompdf->render(); $pdfdata = $dompdf->output();
*/
- 'DOMPDF_CHROOT' => realpath(base_path()),
+ 'chroot' => realpath(base_path()),
/**
* Whether to use Unicode fonts or not.
* When enabled, dompdf can support all Unicode glyphs. Any glyphs used in a
* document must be present in your fonts, however.
*/
- 'DOMPDF_UNICODE_ENABLED' => true,
+ 'unicode_enabled' => true,
/**
* Whether to enable font subsetting or not.
*/
- 'DOMPDF_ENABLE_FONTSUBSETTING' => false,
+ 'enable_fontsubsetting' => false,
/**
* The PDF rendering backend to use.
* @link http://www.ros.co.nz/pdf
* @link http://www.php.net/image
*/
- 'DOMPDF_PDF_BACKEND' => 'CPDF',
+ 'pdf_backend' => 'CPDF',
/**
* PDFlib license key.
* the desired content might be different (e.g. screen or projection view of html file).
* Therefore allow specification of content here.
*/
- 'DOMPDF_DEFAULT_MEDIA_TYPE' => 'print',
+ 'default_media_type' => 'print',
/**
* The default paper size.
*
* @see CPDF_Adapter::PAPER_SIZES for valid sizes ('letter', 'legal', 'A4', etc.)
*/
- 'DOMPDF_DEFAULT_PAPER_SIZE' => 'a4',
+ 'default_paper_size' => 'a4',
/**
* The default font family.
*
* @var string
*/
- 'DOMPDF_DEFAULT_FONT' => 'dejavu sans',
+ 'default_font' => 'dejavu sans',
/**
* Image DPI setting.
*
* @var int
*/
- 'DOMPDF_DPI' => 96,
+ 'dpi' => 96,
/**
* Enable inline PHP.
*
* @var bool
*/
- 'DOMPDF_ENABLE_PHP' => false,
+ 'enable_php' => false,
/**
* Enable inline Javascript.
*
* @var bool
*/
- 'DOMPDF_ENABLE_JAVASCRIPT' => false,
+ 'enable_javascript' => false,
/**
* Enable remote file access.
*
* @var bool
*/
- 'DOMPDF_ENABLE_REMOTE' => true,
+ 'enable_remote' => env('ALLOW_UNTRUSTED_SERVER_FETCHING', false),
/**
* A ratio applied to the fonts height to be more like browsers' line height.
*/
- 'DOMPDF_FONT_HEIGHT_RATIO' => 1.1,
+ 'font_height_ratio' => 1.1,
/**
* Enable CSS float.
*
* @var bool
*/
- 'DOMPDF_ENABLE_CSS_FLOAT' => true,
+ 'enable_css_float' => true,
/**
* Use the more-than-experimental HTML5 Lib parser.
*/
- 'DOMPDF_ENABLE_HTML5PARSER' => true,
+ 'enable_html5parser' => true,
],
namespace BookStack\Console\Commands;
use BookStack\Entities\Tools\SearchIndex;
-use DB;
use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
class RegenerateSearch extends Command
{
--- /dev/null
+<?php
+
+namespace BookStack\Console\Commands;
+
+use BookStack\Auth\User;
+use Illuminate\Console\Command;
+
+class ResetMfa extends Command
+{
+ /**
+ * The name and signature of the console command.
+ *
+ * @var string
+ */
+ protected $signature = 'bookstack:reset-mfa
+ {--id= : Numeric ID of the user to reset MFA for}
+ {--email= : Email address of the user to reset MFA for}
+ ';
+
+ /**
+ * The console command description.
+ *
+ * @var string
+ */
+ protected $description = 'Reset & Clear any configured MFA methods for the given user';
+
+ /**
+ * Create a new command instance.
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ parent::__construct();
+ }
+
+ /**
+ * Execute the console command.
+ *
+ * @return mixed
+ */
+ public function handle()
+ {
+ $id = $this->option('id');
+ $email = $this->option('email');
+ if (!$id && !$email) {
+ $this->error('Either a --id=<number> or --email=<email> option must be provided.');
+
+ return 1;
+ }
+
+ /** @var User $user */
+ $field = $id ? 'id' : 'email';
+ $value = $id ?: $email;
+ $user = User::query()
+ ->where($field, '=', $value)
+ ->first();
+
+ if (!$user) {
+ $this->error("A user where {$field}={$value} could not be found.");
+
+ return 1;
+ }
+
+ $this->info("This will delete any configure multi-factor authentication methods for user: \n- ID: {$user->id}\n- Name: {$user->name}\n- Email: {$user->email}\n");
+ $this->info('If multi-factor authentication is required for this user they will be asked to reconfigure their methods on next login.');
+ $confirm = $this->confirm('Are you sure you want to proceed?');
+ if ($confirm) {
+ $user->mfaValues()->delete();
+ $this->info('User MFA methods have been reset.');
+
+ return 0;
+ }
+
+ return 1;
+ }
+}
/**
* Class Book.
*
- * @property string $description
- * @property int $image_id
- * @property Image|null $cover
+ * @property string $description
+ * @property int $image_id
+ * @property Image|null $cover
+ * @property \Illuminate\Database\Eloquent\Collection $chapters
+ * @property \Illuminate\Database\Eloquent\Collection $pages
+ * @property \Illuminate\Database\Eloquent\Collection $directPages
*/
class Book extends Entity implements HasCoverImage
{
/**
* Class BookChild.
*
- * @property int $book_id
- * @property int $priority
- * @property Book $book
+ * @property int $book_id
+ * @property int $priority
+ * @property string $book_slug
+ * @property Book $book
*
* @method Builder whereSlugs(string $bookSlug, string $childSlug)
*/
abstract class BookChild extends Entity
{
+ protected static function boot()
+ {
+ parent::boot();
+
+ // Load book slugs onto these models by default during query-time
+ static::addGlobalScope('book_slug', function (Builder $builder) {
+ $builder->addSelect(['book_slug' => function ($builder) {
+ $builder->select('slug')
+ ->from('books')
+ ->whereColumn('books.id', '=', 'book_id');
+ }]);
+ });
+ }
+
/**
- * Scope a query to find items where the the child has the given childSlug
+ * Scope a query to find items where the child has the given childSlug
* where its parent has the bookSlug.
*/
public function scopeWhereSlugs(Builder $query, string $bookSlug, string $childSlug)
{
$parts = [
'books',
- urlencode($this->getAttribute('bookSlug') ?? $this->book->slug),
+ urlencode($this->book_slug ?? $this->book->slug),
'chapter',
urlencode($this->slug),
trim($path, '/'),
*/
class Page extends BookChild
{
- protected $fillable = ['name', 'priority', 'markdown'];
+ public static $listAttributes = ['name', 'id', 'slug', 'book_id', 'chapter_id', 'draft', 'template', 'text', 'created_at', 'updated_at', 'priority'];
+ public static $contentAttributes = ['name', 'id', 'slug', 'book_id', 'chapter_id', 'draft', 'template', 'html', 'text', 'created_at', 'updated_at', 'priority'];
- protected $simpleAttributes = ['name', 'id', 'slug'];
+ protected $fillable = ['name', 'priority', 'markdown'];
public $textField = 'text';
return parent::scopeVisible($query);
}
- /**
- * Converts this page into a simplified array.
- *
- * @return mixed
- */
- public function toSimpleArray()
- {
- $array = array_intersect_key($this->toArray(), array_flip($this->simpleAttributes));
- $array['url'] = $this->getUrl();
-
- return $array;
- }
-
/**
* Get the chapter that this page is in, If applicable.
*
{
$parts = [
'books',
- urlencode($this->getAttribute('bookSlug') ?? $this->book->slug),
+ urlencode($this->book_slug ?? $this->book->slug),
$this->draft ? 'draft' : 'page',
$this->draft ? $this->id : urlencode($this->slug),
trim($path, '/'),
$pageContent = new PageContent($page);
if (!empty($input['markdown'] ?? '')) {
$pageContent->setNewMarkdown($input['markdown']);
- } else {
- $pageContent->setNewHTML($input['html'] ?? '');
+ } elseif (isset($input['html'])) {
+ $pageContent->setNewHTML($input['html']);
}
}
*/
public function getTree(bool $showDrafts = false, bool $renderPages = false): Collection
{
- $pages = $this->getPages($showDrafts);
+ $pages = $this->getPages($showDrafts, $renderPages);
$chapters = Chapter::visible()->where('book_id', '=', $this->book->id)->get();
$all = collect()->concat($pages)->concat($chapters);
$chapterMap = $chapters->keyBy('id');
/**
* Get the visible pages within this book.
*/
- protected function getPages(bool $showDrafts = false): Collection
+ protected function getPages(bool $showDrafts = false, bool $getPageContent = false): Collection
{
- $query = Page::visible()->where('book_id', '=', $this->book->id);
+ $query = Page::visible()
+ ->select($getPageContent ? Page::$contentAttributes : Page::$listAttributes)
+ ->where('book_id', '=', $this->book->id);
if (!$showDrafts) {
$query->where('draft', '=', false);
protected function htmlToPdf(string $html): string
{
$containedHtml = $this->containHtml($html);
- $useWKHTML = config('snappy.pdf.binary') !== false;
+ $useWKHTML = config('snappy.pdf.binary') !== false && config('app.allow_untrusted_server_fetching') === true;
if ($useWKHTML) {
$pdf = SnappyPDF::loadHTML($containedHtml);
$pdf->setOption('print-media-type', true);
--- /dev/null
+<?php
+
+namespace BookStack\Entities\Tools\Markdown;
+
+use League\CommonMark\Block\Element\AbstractBlock;
+use League\CommonMark\Block\Element\ListItem;
+use League\CommonMark\Block\Element\Paragraph;
+use League\CommonMark\Block\Renderer\BlockRendererInterface;
+use League\CommonMark\Block\Renderer\ListItemRenderer;
+use League\CommonMark\ElementRendererInterface;
+use League\CommonMark\Extension\TaskList\TaskListItemMarker;
+use League\CommonMark\HtmlElement;
+
+class CustomListItemRenderer implements BlockRendererInterface
+{
+ protected $baseRenderer;
+
+ public function __construct()
+ {
+ $this->baseRenderer = new ListItemRenderer();
+ }
+
+ /**
+ * @return HtmlElement|string|null
+ */
+ public function render(AbstractBlock $block, ElementRendererInterface $htmlRenderer, bool $inTightList = false)
+ {
+ $listItem = $this->baseRenderer->render($block, $htmlRenderer, $inTightList);
+
+ if ($this->startsTaskListItem($block)) {
+ $listItem->setAttribute('class', 'task-list-item');
+ }
+
+ return $listItem;
+ }
+
+ private function startsTaskListItem(ListItem $block): bool
+ {
+ $firstChild = $block->firstChild();
+
+ return $firstChild instanceof Paragraph && $firstChild->firstChild() instanceof TaskListItemMarker;
+ }
+}
namespace BookStack\Entities\Tools;
use BookStack\Entities\Models\Page;
+use BookStack\Entities\Tools\Markdown\CustomListItemRenderer;
use BookStack\Entities\Tools\Markdown\CustomStrikeThroughExtension;
use BookStack\Exceptions\ImageUploadException;
use BookStack\Facades\Theme;
use DOMNodeList;
use DOMXPath;
use Illuminate\Support\Str;
+use League\CommonMark\Block\Element\ListItem;
use League\CommonMark\CommonMarkConverter;
use League\CommonMark\Environment;
use League\CommonMark\Extension\Table\TableExtension;
$environment = Theme::dispatch(ThemeEvents::COMMONMARK_ENVIRONMENT_CONFIGURE, $environment) ?? $environment;
$converter = new CommonMarkConverter([], $environment);
+ $environment->addBlockRenderer(ListItem::class, new CustomListItemRenderer(), 10);
+
return $converter->convertToHtml($markdown);
}
*/
public function render(bool $blankIncludes = false): string
{
- $content = $this->page->html;
+ $content = $this->page->html ?? '';
if (!config('app.allow_content_scripts')) {
$content = HtmlContentFilter::removeScripts($content);
}
// Find page and skip this if page not found
+ /** @var ?Page $matchedPage */
$matchedPage = Page::visible()->find($pageId);
if ($matchedPage === null) {
$html = str_replace($fullMatch, '', $html);
--- /dev/null
+<?php
+
+namespace BookStack\Exceptions;
+
+use BookStack\Auth\Access\LoginService;
+use BookStack\Auth\User;
+use Illuminate\Contracts\Support\Responsable;
+use Illuminate\Http\Request;
+
+class StoppedAuthenticationException extends \Exception implements Responsable
+{
+ protected $user;
+ protected $loginService;
+
+ /**
+ * StoppedAuthenticationException constructor.
+ */
+ public function __construct(User $user, LoginService $loginService)
+ {
+ $this->user = $user;
+ $this->loginService = $loginService;
+ parent::__construct();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function toResponse($request)
+ {
+ $redirect = '/login';
+
+ if ($this->loginService->awaitingEmailConfirmation($this->user)) {
+ return $this->awaitingEmailConfirmationResponse($request);
+ }
+
+ if ($this->loginService->needsMfaVerification($this->user)) {
+ $redirect = '/mfa/verify';
+ }
+
+ return redirect($redirect);
+ }
+
+ /**
+ * Provide an error response for when the current user's email is not confirmed
+ * in a system which requires it.
+ */
+ protected function awaitingEmailConfirmationResponse(Request $request)
+ {
+ if ($request->wantsJson()) {
+ return response()->json([
+ 'error' => [
+ 'code' => 401,
+ 'message' => trans('errors.email_confirmation_awaiting'),
+ ],
+ ], 401);
+ }
+
+ if (session()->pull('sent-email-confirmation') === true) {
+ return redirect('/register/confirm');
+ }
+
+ return redirect('/register/confirm/awaiting');
+ }
+}
public function __construct(ExportFormatter $exportFormatter)
{
$this->exportFormatter = $exportFormatter;
+ $this->middleware('can:content-export');
}
/**
public function __construct(ExportFormatter $exportFormatter)
{
$this->exportFormatter = $exportFormatter;
+ $this->middleware('can:content-export');
}
/**
public function __construct(ExportFormatter $exportFormatter)
{
$this->exportFormatter = $exportFormatter;
+ $this->middleware('can:content-export');
}
/**
namespace BookStack\Http\Controllers\Auth;
-use BookStack\Actions\ActivityType;
use BookStack\Auth\Access\EmailConfirmationService;
+use BookStack\Auth\Access\LoginService;
use BookStack\Auth\UserRepo;
use BookStack\Exceptions\ConfirmationEmailException;
use BookStack\Exceptions\UserTokenExpiredException;
use BookStack\Exceptions\UserTokenNotFoundException;
-use BookStack\Facades\Theme;
use BookStack\Http\Controllers\Controller;
-use BookStack\Theming\ThemeEvents;
use Exception;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class ConfirmEmailController extends Controller
{
protected $emailConfirmationService;
+ protected $loginService;
protected $userRepo;
/**
* Create a new controller instance.
*/
- public function __construct(EmailConfirmationService $emailConfirmationService, UserRepo $userRepo)
- {
+ public function __construct(
+ EmailConfirmationService $emailConfirmationService,
+ LoginService $loginService,
+ UserRepo $userRepo
+ ) {
$this->emailConfirmationService = $emailConfirmationService;
+ $this->loginService = $loginService;
$this->userRepo = $userRepo;
}
/**
* Shows a notice that a user's email address has not been confirmed,
* Also has the option to re-send the confirmation email.
- *
- * @return View
*/
public function showAwaiting()
{
- return view('auth.user-unconfirmed');
+ $user = $this->loginService->getLastLoginAttemptUser();
+
+ return view('auth.user-unconfirmed', ['user' => $user]);
}
/**
$user->email_confirmed = true;
$user->save();
- auth()->login($user);
- Theme::dispatch(ThemeEvents::AUTH_LOGIN, auth()->getDefaultDriver(), $user);
- $this->logActivity(ActivityType::AUTH_LOGIN, $user);
- $this->showSuccessNotification(trans('auth.email_confirm_success'));
$this->emailConfirmationService->deleteByUser($user);
+ $this->showSuccessNotification(trans('auth.email_confirm_success'));
+ $this->loginService->login($user, auth()->getDefaultDriver());
return redirect('/');
}
--- /dev/null
+<?php
+
+namespace BookStack\Http\Controllers\Auth;
+
+use BookStack\Auth\Access\LoginService;
+use BookStack\Auth\User;
+use BookStack\Exceptions\NotFoundException;
+
+trait HandlesPartialLogins
+{
+ /**
+ * @throws NotFoundException
+ */
+ protected function currentOrLastAttemptedUser(): User
+ {
+ $loginService = app()->make(LoginService::class);
+ $user = auth()->user() ?? $loginService->getLastLoginAttemptUser();
+
+ if (!$user) {
+ throw new NotFoundException('A user for this action could not be found');
+ }
+
+ return $user;
+ }
+}
namespace BookStack\Http\Controllers\Auth;
use Activity;
-use BookStack\Actions\ActivityType;
+use BookStack\Auth\Access\LoginService;
use BookStack\Auth\Access\SocialAuthService;
use BookStack\Exceptions\LoginAttemptEmailNeededException;
use BookStack\Exceptions\LoginAttemptException;
-use BookStack\Facades\Theme;
use BookStack\Http\Controllers\Controller;
-use BookStack\Theming\ThemeEvents;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
protected $redirectAfterLogout = '/login';
protected $socialAuthService;
+ protected $loginService;
/**
* Create a new controller instance.
*/
- public function __construct(SocialAuthService $socialAuthService)
+ public function __construct(SocialAuthService $socialAuthService, LoginService $loginService)
{
$this->middleware('guest', ['only' => ['getLogin', 'login']]);
$this->middleware('guard:standard,ldap', ['only' => ['login', 'logout']]);
$this->socialAuthService = $socialAuthService;
+ $this->loginService = $loginService;
+
$this->redirectPath = url('/');
$this->redirectAfterLogout = url('/login');
}
}
// Store the previous location for redirect after login
- $previous = url()->previous('');
- if ($previous && $previous !== url('/login') && setting('app-public')) {
- $isPreviousFromInstance = (strpos($previous, url('/')) === 0);
- if ($isPreviousFromInstance) {
- redirect()->setIntendedUrl($previous);
- }
- }
+ $this->updateIntendedFromPrevious();
return view('auth.login', [
'socialDrivers' => $socialDrivers,
return $this->sendFailedLoginResponse($request);
}
+ /**
+ * Attempt to log the user into the application.
+ *
+ * @param \Illuminate\Http\Request $request
+ *
+ * @return bool
+ */
+ protected function attemptLogin(Request $request)
+ {
+ return $this->loginService->attempt(
+ $this->credentials($request),
+ auth()->getDefaultDriver(),
+ $request->filled('remember')
+ );
+ }
+
/**
* The user has been authenticated.
*
*/
protected function authenticated(Request $request, $user)
{
- // Authenticate on all session guards if a likely admin
- if ($user->can('users-manage') && $user->can('user-roles-manage')) {
- $guards = ['standard', 'ldap', 'saml2'];
- foreach ($guards as $guard) {
- auth($guard)->login($user);
- }
- }
-
- Theme::dispatch(ThemeEvents::AUTH_LOGIN, auth()->getDefaultDriver(), $user);
- $this->logActivity(ActivityType::AUTH_LOGIN, $user);
-
return redirect()->intended($this->redirectPath());
}
$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
+ * login or authentication routes.
+ */
+ protected function updateIntendedFromPrevious(): void
+ {
+ // Store the previous location for redirect after login
+ $previous = url()->previous('');
+ $isPreviousFromInstance = (strpos($previous, url('/')) === 0);
+ if (!$previous || !setting('app-public') || !$isPreviousFromInstance) {
+ return;
+ }
+
+ $ignorePrefixList = [
+ '/login',
+ '/mfa',
+ ];
+
+ foreach ($ignorePrefixList as $ignorePrefix) {
+ if (strpos($previous, url($ignorePrefix)) === 0) {
+ return;
+ }
+ }
+
+ redirect()->setIntendedUrl($previous);
+ }
}
--- /dev/null
+<?php
+
+namespace BookStack\Http\Controllers\Auth;
+
+use BookStack\Actions\ActivityType;
+use BookStack\Auth\Access\LoginService;
+use BookStack\Auth\Access\Mfa\BackupCodeService;
+use BookStack\Auth\Access\Mfa\MfaSession;
+use BookStack\Auth\Access\Mfa\MfaValue;
+use BookStack\Exceptions\NotFoundException;
+use BookStack\Http\Controllers\Controller;
+use Exception;
+use Illuminate\Http\Request;
+use Illuminate\Validation\ValidationException;
+
+class MfaBackupCodesController extends Controller
+{
+ use HandlesPartialLogins;
+
+ protected const SETUP_SECRET_SESSION_KEY = 'mfa-setup-backup-codes';
+
+ /**
+ * Show a view that generates and displays backup codes.
+ */
+ public function generate(BackupCodeService $codeService)
+ {
+ $codes = $codeService->generateNewSet();
+ session()->put(self::SETUP_SECRET_SESSION_KEY, encrypt($codes));
+
+ $downloadUrl = 'data:application/octet-stream;base64,' . base64_encode(implode("\n\n", $codes));
+
+ return view('mfa.backup-codes-generate', [
+ 'codes' => $codes,
+ 'downloadUrl' => $downloadUrl,
+ ]);
+ }
+
+ /**
+ * Confirm the setup of backup codes, storing them against the user.
+ *
+ * @throws Exception
+ */
+ public function confirm()
+ {
+ if (!session()->has(self::SETUP_SECRET_SESSION_KEY)) {
+ return response('No generated codes found in the session', 500);
+ }
+
+ $codes = decrypt(session()->pull(self::SETUP_SECRET_SESSION_KEY));
+ MfaValue::upsertWithValue($this->currentOrLastAttemptedUser(), MfaValue::METHOD_BACKUP_CODES, json_encode($codes));
+
+ $this->logActivity(ActivityType::MFA_SETUP_METHOD, 'backup-codes');
+
+ if (!auth()->check()) {
+ $this->showSuccessNotification(trans('auth.mfa_setup_login_notification'));
+
+ return redirect('/login');
+ }
+
+ return redirect('/mfa/setup');
+ }
+
+ /**
+ * Verify the MFA method submission on check.
+ *
+ * @throws NotFoundException
+ * @throws ValidationException
+ */
+ public function verify(Request $request, BackupCodeService $codeService, MfaSession $mfaSession, LoginService $loginService)
+ {
+ $user = $this->currentOrLastAttemptedUser();
+ $codes = MfaValue::getValueForUser($user, MfaValue::METHOD_BACKUP_CODES) ?? '[]';
+
+ $this->validate($request, [
+ 'code' => [
+ 'required',
+ 'max:12', 'min:8',
+ function ($attribute, $value, $fail) use ($codeService, $codes) {
+ if (!$codeService->inputCodeExistsInSet($value, $codes)) {
+ $fail(trans('validation.backup_codes'));
+ }
+ },
+ ],
+ ]);
+
+ $updatedCodes = $codeService->removeInputCodeFromSet($request->get('code'), $codes);
+ MfaValue::upsertWithValue($user, MfaValue::METHOD_BACKUP_CODES, $updatedCodes);
+
+ $mfaSession->markVerifiedForUser($user);
+ $loginService->reattemptLoginFor($user);
+
+ if ($codeService->countCodesInSet($updatedCodes) < 5) {
+ $this->showWarningNotification(trans('auth.mfa_backup_codes_usage_limit_warning'));
+ }
+
+ return redirect()->intended();
+ }
+}
--- /dev/null
+<?php
+
+namespace BookStack\Http\Controllers\Auth;
+
+use BookStack\Actions\ActivityType;
+use BookStack\Auth\Access\Mfa\MfaValue;
+use BookStack\Http\Controllers\Controller;
+use Illuminate\Http\Request;
+
+class MfaController extends Controller
+{
+ use HandlesPartialLogins;
+
+ /**
+ * Show the view to setup MFA for the current user.
+ */
+ public function setup()
+ {
+ $userMethods = $this->currentOrLastAttemptedUser()
+ ->mfaValues()
+ ->get(['id', 'method'])
+ ->groupBy('method');
+
+ return view('mfa.setup', [
+ 'userMethods' => $userMethods,
+ ]);
+ }
+
+ /**
+ * Remove an MFA method for the current user.
+ *
+ * @throws \Exception
+ */
+ public function remove(string $method)
+ {
+ if (in_array($method, MfaValue::allMethods())) {
+ $value = user()->mfaValues()->where('method', '=', $method)->first();
+ if ($value) {
+ $value->delete();
+ $this->logActivity(ActivityType::MFA_REMOVE_METHOD, $method);
+ }
+ }
+
+ return redirect('/mfa/setup');
+ }
+
+ /**
+ * Show the page to start an MFA verification.
+ */
+ public function verify(Request $request)
+ {
+ $desiredMethod = $request->get('method');
+ $userMethods = $this->currentOrLastAttemptedUser()
+ ->mfaValues()
+ ->get(['id', 'method'])
+ ->groupBy('method');
+
+ // Basic search for the default option for a user.
+ // (Prioritises totp over backup codes)
+ $method = $userMethods->has($desiredMethod) ? $desiredMethod : $userMethods->keys()->sort()->reverse()->first();
+ $otherMethods = $userMethods->keys()->filter(function ($userMethod) use ($method) {
+ return $method !== $userMethod;
+ })->all();
+
+ return view('mfa.verify', [
+ 'userMethods' => $userMethods,
+ 'method' => $method,
+ 'otherMethods' => $otherMethods,
+ ]);
+ }
+}
--- /dev/null
+<?php
+
+namespace BookStack\Http\Controllers\Auth;
+
+use BookStack\Actions\ActivityType;
+use BookStack\Auth\Access\LoginService;
+use BookStack\Auth\Access\Mfa\MfaSession;
+use BookStack\Auth\Access\Mfa\MfaValue;
+use BookStack\Auth\Access\Mfa\TotpService;
+use BookStack\Auth\Access\Mfa\TotpValidationRule;
+use BookStack\Exceptions\NotFoundException;
+use BookStack\Http\Controllers\Controller;
+use Illuminate\Http\Request;
+use Illuminate\Validation\ValidationException;
+
+class MfaTotpController extends Controller
+{
+ use HandlesPartialLogins;
+
+ protected const SETUP_SECRET_SESSION_KEY = 'mfa-setup-totp-secret';
+
+ /**
+ * Show a view that generates and displays a TOTP QR code.
+ */
+ public function generate(TotpService $totp)
+ {
+ if (session()->has(static::SETUP_SECRET_SESSION_KEY)) {
+ $totpSecret = decrypt(session()->get(static::SETUP_SECRET_SESSION_KEY));
+ } else {
+ $totpSecret = $totp->generateSecret();
+ session()->put(static::SETUP_SECRET_SESSION_KEY, encrypt($totpSecret));
+ }
+
+ $qrCodeUrl = $totp->generateUrl($totpSecret);
+ $svg = $totp->generateQrCodeSvg($qrCodeUrl);
+
+ return view('mfa.totp-generate', [
+ 'url' => $qrCodeUrl,
+ 'svg' => $svg,
+ ]);
+ }
+
+ /**
+ * Confirm the setup of TOTP and save the auth method secret
+ * against the current user.
+ *
+ * @throws ValidationException
+ * @throws NotFoundException
+ */
+ public function confirm(Request $request)
+ {
+ $totpSecret = decrypt(session()->get(static::SETUP_SECRET_SESSION_KEY));
+ $this->validate($request, [
+ 'code' => [
+ 'required',
+ 'max:12', 'min:4',
+ new TotpValidationRule($totpSecret),
+ ],
+ ]);
+
+ MfaValue::upsertWithValue($this->currentOrLastAttemptedUser(), MfaValue::METHOD_TOTP, $totpSecret);
+ session()->remove(static::SETUP_SECRET_SESSION_KEY);
+ $this->logActivity(ActivityType::MFA_SETUP_METHOD, 'totp');
+
+ if (!auth()->check()) {
+ $this->showSuccessNotification(trans('auth.mfa_setup_login_notification'));
+
+ return redirect('/login');
+ }
+
+ return redirect('/mfa/setup');
+ }
+
+ /**
+ * Verify the MFA method submission on check.
+ *
+ * @throws NotFoundException
+ */
+ public function verify(Request $request, LoginService $loginService, MfaSession $mfaSession)
+ {
+ $user = $this->currentOrLastAttemptedUser();
+ $totpSecret = MfaValue::getValueForUser($user, MfaValue::METHOD_TOTP);
+
+ $this->validate($request, [
+ 'code' => [
+ 'required',
+ 'max:12', 'min:4',
+ new TotpValidationRule($totpSecret),
+ ],
+ ]);
+
+ $mfaSession->markVerifiedForUser($user);
+ $loginService->reattemptLoginFor($user);
+
+ return redirect()->intended();
+ }
+}
namespace BookStack\Http\Controllers\Auth;
-use BookStack\Actions\ActivityType;
+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\Facades\Theme;
use BookStack\Http\Controllers\Controller;
-use BookStack\Theming\ThemeEvents;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
-use Validator;
+use Illuminate\Support\Facades\Validator;
class RegisterController extends Controller
{
protected $socialAuthService;
protected $registrationService;
+ protected $loginService;
/**
* Where to redirect users after login / registration.
/**
* Create a new controller instance.
*/
- public function __construct(SocialAuthService $socialAuthService, RegistrationService $registrationService)
- {
+ public function __construct(
+ SocialAuthService $socialAuthService,
+ RegistrationService $registrationService,
+ LoginService $loginService
+ ) {
$this->middleware('guest');
$this->middleware('guard:standard');
$this->socialAuthService = $socialAuthService;
$this->registrationService = $registrationService;
+ $this->loginService = $loginService;
$this->redirectTo = url('/');
$this->redirectPath = url('/');
* Handle a registration request for the application.
*
* @throws UserRegistrationException
+ * @throws StoppedAuthenticationException
*/
public function postRegister(Request $request)
{
try {
$user = $this->registrationService->registerUser($userData);
- auth()->login($user);
- Theme::dispatch(ThemeEvents::AUTH_LOGIN, auth()->getDefaultDriver(), $user);
- $this->logActivity(ActivityType::AUTH_LOGIN, $user);
+ $this->loginService->login($user, auth()->getDefaultDriver());
} catch (UserRegistrationException $exception) {
if ($exception->getMessage()) {
$this->showErrorNotification($exception->getMessage());
namespace BookStack\Http\Controllers\Auth;
-use BookStack\Actions\ActivityType;
+use BookStack\Auth\Access\LoginService;
use BookStack\Auth\Access\RegistrationService;
use BookStack\Auth\Access\SocialAuthService;
use BookStack\Exceptions\SocialDriverNotConfigured;
use BookStack\Exceptions\SocialSignInAccountNotUsed;
use BookStack\Exceptions\SocialSignInException;
use BookStack\Exceptions\UserRegistrationException;
-use BookStack\Facades\Theme;
use BookStack\Http\Controllers\Controller;
-use BookStack\Theming\ThemeEvents;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Laravel\Socialite\Contracts\User as SocialUser;
{
protected $socialAuthService;
protected $registrationService;
+ protected $loginService;
/**
* SocialController constructor.
*/
- public function __construct(SocialAuthService $socialAuthService, RegistrationService $registrationService)
- {
+ public function __construct(
+ SocialAuthService $socialAuthService,
+ RegistrationService $registrationService,
+ LoginService $loginService
+ ) {
$this->middleware('guest')->only(['getRegister', 'postRegister']);
$this->socialAuthService = $socialAuthService;
$this->registrationService = $registrationService;
+ $this->loginService = $loginService;
}
/**
}
$user = $this->registrationService->registerUser($userData, $socialAccount, $emailVerified);
- auth()->login($user);
- Theme::dispatch(ThemeEvents::AUTH_LOGIN, $socialDriver, $user);
- $this->logActivity(ActivityType::AUTH_LOGIN, $user);
-
$this->showSuccessNotification(trans('auth.register_success'));
+ $this->loginService->login($user, $socialDriver);
return redirect('/');
}
namespace BookStack\Http\Controllers\Auth;
-use BookStack\Actions\ActivityType;
+use BookStack\Auth\Access\LoginService;
use BookStack\Auth\Access\UserInviteService;
use BookStack\Auth\UserRepo;
use BookStack\Exceptions\UserTokenExpiredException;
use BookStack\Exceptions\UserTokenNotFoundException;
-use BookStack\Facades\Theme;
use BookStack\Http\Controllers\Controller;
-use BookStack\Theming\ThemeEvents;
use Exception;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class UserInviteController extends Controller
{
protected $inviteService;
+ protected $loginService;
protected $userRepo;
/**
* Create a new controller instance.
*/
- public function __construct(UserInviteService $inviteService, UserRepo $userRepo)
+ public function __construct(UserInviteService $inviteService, LoginService $loginService, UserRepo $userRepo)
{
$this->middleware('guest');
$this->middleware('guard:standard');
$this->inviteService = $inviteService;
+ $this->loginService = $loginService;
$this->userRepo = $userRepo;
}
$user->email_confirmed = true;
$user->save();
- auth()->login($user);
- Theme::dispatch(ThemeEvents::AUTH_LOGIN, auth()->getDefaultDriver(), $user);
- $this->logActivity(ActivityType::AUTH_LOGIN, $user);
- $this->showSuccessNotification(trans('auth.user_invite_success', ['appName' => setting('app-name')]));
$this->inviteService->deleteByUser($user);
+ $this->showSuccessNotification(trans('auth.user_invite_success', ['appName' => setting('app-name')]));
+ $this->loginService->login($user, auth()->getDefaultDriver());
return redirect('/');
}
{
$this->bookRepo = $bookRepo;
$this->exportFormatter = $exportFormatter;
+ $this->middleware('can:content-export');
}
/**
$book = $this->bookRepo->getBySlug($bookSlug);
$bookChildren = (new BookContents($book))->getTree();
- return view('books.sort-box', ['book' => $book, 'bookChildren' => $bookChildren]);
+ return view('books.parts.sort-box', ['book' => $book, 'bookChildren' => $bookChildren]);
}
/**
{
$this->chapterRepo = $chapterRepo;
$this->exportFormatter = $exportFormatter;
+ $this->middleware('can:content-export');
}
/**
use BookStack\Entities\Repos\BookRepo;
use BookStack\Entities\Repos\BookshelfRepo;
use BookStack\Entities\Tools\PageContent;
-use Views;
class HomeController extends Controller
{
->where('draft', false)
->orderBy('updated_at', 'desc')
->take($favourites->count() > 0 ? 6 : 12)
+ ->select(Page::$listAttributes)
->get();
$homepageOptions = ['default', 'books', 'bookshelves', 'page'];
$shelves = app(BookshelfRepo::class)->getAllPaginated(18, $commonData['sort'], $commonData['order']);
$data = array_merge($commonData, ['shelves' => $shelves]);
- return view('common.home-shelves', $data);
+ return view('home.shelves', $data);
}
if ($homepageOption === 'books') {
$books = $bookRepo->getAllPaginated(18, $commonData['sort'], $commonData['order']);
$data = array_merge($commonData, ['books' => $books]);
- return view('common.home-book', $data);
+ return view('home.books', $data);
}
if ($homepageOption === 'page') {
$homepageSetting = setting('app-homepage', '0:');
$id = intval(explode(':', $homepageSetting)[0]);
+ /** @var Page $customHomepage */
$customHomepage = Page::query()->where('draft', '=', false)->findOrFail($id);
$pageContent = new PageContent($customHomepage);
- $customHomepage->html = $pageContent->render(true);
+ $customHomepage->html = $pageContent->render(false);
- return view('common.home-custom', array_merge($commonData, ['customHomepage' => $customHomepage]));
+ return view('home.specific-page', array_merge($commonData, ['customHomepage' => $customHomepage]));
}
- return view('common.home', $commonData);
+ return view('home.default', $commonData);
}
/**
* Get custom head HTML, Used in ajax calls to show in editor.
- *
- * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function customHeadContent()
{
- return view('partials.custom-head');
+ return view('common.custom-head');
}
/**
* Show the view for /robots.txt.
*/
- public function getRobots()
+ public function robots()
{
$sitePublic = setting('app-public', false);
$allowRobots = config('app.allow_robots');
}
return response()
- ->view('common.robots', ['allowRobots' => $allowRobots])
+ ->view('misc.robots', ['allowRobots' => $allowRobots])
->header('Content-Type', 'text/plain');
}
/**
* Show the route for 404 responses.
*/
- public function getNotFound()
+ public function notFound()
{
return response()->view('errors.404', [], 404);
}
$imgData = $this->imageRepo->getEntityFiltered('drawio', $parentTypeFilter, $page, 24, $uploadedToFilter, $searchTerm);
- return view('components.image-manager-list', [
+ return view('pages.parts.image-manager-list', [
'images' => $imgData['images'],
'hasMore' => $imgData['has_more'],
]);
$imgData = $this->imageRepo->getEntityFiltered('gallery', $parentTypeFilter, $page, 24, $uploadedToFilter, $searchTerm);
- return view('components.image-manager-list', [
+ return view('pages.parts.image-manager-list', [
'images' => $imgData['images'],
'hasMore' => $imgData['has_more'],
]);
$this->imageRepo->loadThumbs($image);
- return view('components.image-manager-form', [
+ return view('pages.parts.image-manager-form', [
'image' => $image,
'dependantPages' => null,
]);
$this->imageRepo->loadThumbs($image);
- return view('components.image-manager-form', [
+ return view('pages.parts.image-manager-form', [
'image' => $image,
'dependantPages' => $dependantPages ?? null,
]);
{
$this->pageRepo = $pageRepo;
$this->exportFormatter = $exportFormatter;
+ $this->middleware('can:content-export');
}
/**
$templates->appends(['search' => $search]);
}
- return view('pages.template-manager-list', [
+ return view('pages.parts.template-manager-list', [
'templates' => $templates,
]);
}
$term = $request->get('term', '');
$results = $this->searchRunner->searchBook($bookId, $term);
- return view('partials.entity-list', ['entities' => $results]);
+ return view('entities.list', ['entities' => $results]);
}
/**
$term = $request->get('term', '');
$results = $this->searchRunner->searchChapter($chapterId, $term);
- return view('partials.entity-list', ['entities' => $results]);
+ return view('entities.list', ['entities' => $results]);
}
/**
$entities = (new Popular())->run(20, 0, $entityTypes, $permission);
}
- return view('search.entity-ajax-list', ['entities' => $entities]);
+ return view('search.parts.entity-ajax-list', ['entities' => $entities]);
}
/**
$entities = (new SiblingFetcher())->fetch($type, $id);
- return view('partials.entity-list-basic', ['entities' => $entities, 'style' => 'compact']);
+ return view('entities.list-basic', ['entities' => $entities, 'style' => 'compact']);
}
}
{
$this->checkPermissionOrCurrentUser('users-manage', $id);
- $user = $this->user->newQuery()->with(['apiTokens'])->findOrFail($id);
+ /** @var User $user */
+ $user = $this->user->newQuery()->with(['apiTokens', 'mfaValues'])->findOrFail($id);
$authMethod = ($user->system_name) ? 'system' : config('auth.method');
$activeSocialDrivers = $socialAuthService->getActiveDrivers();
+ $mfaMethods = $user->mfaValues->groupBy('method');
$this->setPageTitle(trans('settings.user_profile'));
$roles = $this->userRepo->getAllRoles();
return view('users.edit', [
'user' => $user,
'activeSocialDrivers' => $activeSocialDrivers,
+ 'mfaMethods' => $mfaMethods,
'authMethod' => $authMethod,
'roles' => $roles,
]);
$users = $query->get();
- return view('components.user-select-list', compact('users'));
+ return view('form.user-select-list', compact('users'));
}
}
*/
protected $middlewareGroups = [
'web' => [
- \BookStack\Http\Middleware\ControlIframeSecurity::class,
+ \BookStack\Http\Middleware\ApplyCspRules::class,
\BookStack\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\BookStack\Http\Middleware\VerifyCsrfToken::class,
+ \BookStack\Http\Middleware\CheckEmailConfirmed::class,
\BookStack\Http\Middleware\RunThemeActions::class,
\BookStack\Http\Middleware\Localization::class,
],
\BookStack\Http\Middleware\EncryptCookies::class,
\BookStack\Http\Middleware\StartSessionIfCookieExists::class,
\BookStack\Http\Middleware\ApiAuthenticate::class,
+ \BookStack\Http\Middleware\CheckEmailConfirmed::class,
],
];
*/
protected $routeMiddleware = [
'auth' => \BookStack\Http\Middleware\Authenticate::class,
- 'can' => \Illuminate\Auth\Middleware\Authorize::class,
+ 'can' => \BookStack\Http\Middleware\CheckUserHasPermission::class,
'guest' => \BookStack\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
- 'perm' => \BookStack\Http\Middleware\PermissionMiddleware::class,
'guard' => \BookStack\Http\Middleware\CheckGuard::class,
+ 'mfa-setup' => \BookStack\Http\Middleware\AuthenticatedOrPendingMfa::class,
];
}
class ApiAuthenticate
{
- use ChecksForEmailConfirmation;
-
/**
* Handle an incoming request.
*/
// Return if the user is already found to be signed in via session-based auth.
// This is to make it easy to browser the API via browser after just logging into the system.
if (signedInUser() || session()->isStarted()) {
- $this->ensureEmailConfirmedIfRequested();
if (!user()->can('access-api')) {
throw new ApiAuthException(trans('errors.api_user_no_api_permission'), 403);
}
// Validate the token and it's users API access
auth()->authenticate();
- $this->ensureEmailConfirmedIfRequested();
}
/**
--- /dev/null
+<?php
+
+namespace BookStack\Http\Middleware;
+
+use BookStack\Util\CspService;
+use Closure;
+use Illuminate\Http\Request;
+
+class ApplyCspRules
+{
+ /**
+ * @var CspService
+ */
+ protected $cspService;
+
+ public function __construct(CspService $cspService)
+ {
+ $this->cspService = $cspService;
+ }
+
+ /**
+ * Handle an incoming request.
+ *
+ * @param Request $request
+ * @param Closure $next
+ *
+ * @return mixed
+ */
+ public function handle($request, Closure $next)
+ {
+ view()->share('cspNonce', $this->cspService->getNonce());
+ if ($this->cspService->allowedIFrameHostsConfigured()) {
+ config()->set('session.same_site', 'none');
+ }
+
+ $response = $next($request);
+
+ $this->cspService->setFrameAncestors($response);
+ $this->cspService->setScriptSrc($response);
+ $this->cspService->setObjectSrc($response);
+ $this->cspService->setBaseUri($response);
+
+ return $response;
+ }
+}
class Authenticate
{
- use ChecksForEmailConfirmation;
-
/**
* Handle an incoming request.
*/
public function handle(Request $request, Closure $next)
{
- if ($this->awaitingEmailConfirmation()) {
- return $this->emailConfirmationErrorResponse($request);
- }
-
if (!hasAppAccess()) {
if ($request->ajax()) {
return response('Unauthorized.', 401);
- } else {
- return redirect()->guest(url('/login'));
}
- }
-
- return $next($request);
- }
- /**
- * Provide an error response for when the current user's email is not confirmed
- * in a system which requires it.
- */
- protected function emailConfirmationErrorResponse(Request $request)
- {
- if ($request->wantsJson()) {
- return response()->json([
- 'error' => [
- 'code' => 401,
- 'message' => trans('errors.email_confirmation_awaiting'),
- ],
- ], 401);
+ return redirect()->guest(url('/login'));
}
- if (session()->get('sent-email-confirmation') === true) {
- return redirect('/register/confirm');
- }
-
- return redirect('/register/confirm/awaiting');
+ return $next($request);
}
}
--- /dev/null
+<?php
+
+namespace BookStack\Http\Middleware;
+
+use BookStack\Auth\Access\LoginService;
+use BookStack\Auth\Access\Mfa\MfaSession;
+use Closure;
+
+class AuthenticatedOrPendingMfa
+{
+ protected $loginService;
+ protected $mfaSession;
+
+ public function __construct(LoginService $loginService, MfaSession $mfaSession)
+ {
+ $this->loginService = $loginService;
+ $this->mfaSession = $mfaSession;
+ }
+
+ /**
+ * Handle an incoming request.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @param \Closure $next
+ *
+ * @return mixed
+ */
+ public function handle($request, Closure $next)
+ {
+ $user = auth()->user();
+ $loggedIn = $user !== null;
+ $lastAttemptUser = $this->loginService->getLastLoginAttemptUser();
+
+ if ($loggedIn || ($lastAttemptUser && $this->mfaSession->isPendingMfaSetup($lastAttemptUser))) {
+ return $next($request);
+ }
+
+ return redirect()->to(url('/login'));
+ }
+}
--- /dev/null
+<?php
+
+namespace BookStack\Http\Middleware;
+
+use BookStack\Auth\Access\EmailConfirmationService;
+use BookStack\Auth\User;
+use Closure;
+
+/**
+ * Check that the user's email address is confirmed.
+ *
+ * As of v21.08 this is technically not required but kept as a prevention
+ * to log out any users that may be logged in but in an "awaiting confirmation" state.
+ * We'll keep this for a while until it'd be very unlikely for a user to be upgrading from
+ * a pre-v21.08 version.
+ *
+ * Ideally we'd simply invalidate all existing sessions upon update but that has
+ * proven to be a lot more difficult than expected.
+ */
+class CheckEmailConfirmed
+{
+ protected $confirmationService;
+
+ public function __construct(EmailConfirmationService $confirmationService)
+ {
+ $this->confirmationService = $confirmationService;
+ }
+
+ /**
+ * Handle an incoming request.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @param \Closure $next
+ *
+ * @return mixed
+ */
+ public function handle($request, Closure $next)
+ {
+ /** @var User $user */
+ $user = auth()->user();
+ if (auth()->check() && !$user->email_confirmed && $this->confirmationService->confirmationRequired()) {
+ auth()->logout();
+
+ return redirect()->to('/');
+ }
+
+ return $next($request);
+ }
+}
--- /dev/null
+<?php
+
+namespace BookStack\Http\Middleware;
+
+use Closure;
+use Illuminate\Http\Request;
+
+class CheckUserHasPermission
+{
+ /**
+ * Handle an incoming request.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @param \Closure $next
+ * @param $permission
+ *
+ * @return mixed
+ */
+ public function handle($request, Closure $next, $permission)
+ {
+ if (!user()->can($permission)) {
+ return $this->errorResponse($request);
+ }
+
+ return $next($request);
+ }
+
+ protected function errorResponse(Request $request)
+ {
+ if ($request->wantsJson()) {
+ return response()->json(['error' => trans('errors.permissionJson')], 403);
+ }
+
+ session()->flash('error', trans('errors.permission'));
+
+ return redirect('/');
+ }
+}
+++ /dev/null
-<?php
-
-namespace BookStack\Http\Middleware;
-
-use BookStack\Exceptions\UnauthorizedException;
-
-trait ChecksForEmailConfirmation
-{
- /**
- * Check if the current user has a confirmed email if the instance deems it as required.
- * Throws if confirmation is required by the user.
- *
- * @throws UnauthorizedException
- */
- protected function ensureEmailConfirmedIfRequested()
- {
- if ($this->awaitingEmailConfirmation()) {
- throw new UnauthorizedException(trans('errors.email_confirmation_awaiting'));
- }
- }
-
- /**
- * Check if email confirmation is required and the current user is awaiting confirmation.
- */
- protected function awaitingEmailConfirmation(): bool
- {
- if (auth()->check()) {
- $requireConfirmation = (setting('registration-confirmation') || setting('registration-restrict'));
- if ($requireConfirmation && !auth()->user()->email_confirmed) {
- return true;
- }
- }
-
- return false;
- }
-}
+++ /dev/null
-<?php
-
-namespace BookStack\Http\Middleware;
-
-use Closure;
-
-/**
- * Sets CSP headers to restrict the hosts that BookStack can be
- * iframed within. Also adjusts the cookie samesite options
- * so that cookies will operate in the third-party context.
- */
-class ControlIframeSecurity
-{
- /**
- * Handle an incoming request.
- *
- * @param \Illuminate\Http\Request $request
- * @param \Closure $next
- *
- * @return mixed
- */
- public function handle($request, Closure $next)
- {
- $iframeHosts = collect(explode(' ', config('app.iframe_hosts', '')))->filter();
- if ($iframeHosts->count() > 0) {
- config()->set('session.same_site', 'none');
- }
-
- $iframeHosts->prepend("'self'");
-
- $response = $next($request);
- $cspValue = 'frame-ancestors ' . $iframeHosts->join(' ');
- $response->headers->set('Content-Security-Policy', $cspValue);
-
- return $response;
- }
-}
'it' => 'it_IT',
'ja' => 'ja',
'ko' => 'ko_KR',
+ 'lt' => 'lt_LT',
'lv' => 'lv_LV',
'nl' => 'nl_NL',
'nb' => 'nb_NO',
+++ /dev/null
-<?php
-
-namespace BookStack\Http\Middleware;
-
-use Closure;
-
-class PermissionMiddleware
-{
- /**
- * Handle an incoming request.
- *
- * @param \Illuminate\Http\Request $request
- * @param \Closure $next
- * @param $permission
- *
- * @return mixed
- */
- public function handle($request, Closure $next, $permission)
- {
- if (!$request->user() || !$request->user()->can($permission)) {
- session()->flash('error', trans('errors.permission'));
-
- return redirect()->back();
- }
-
- return $next($request);
- }
-}
namespace BookStack\Providers;
-use Blade;
+use BookStack\Auth\Access\LoginService;
use BookStack\Auth\Access\SocialAuthService;
use BookStack\Entities\BreadcrumbsViewComposer;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Page;
use BookStack\Settings\Setting;
use BookStack\Settings\SettingService;
+use BookStack\Util\CspService;
use Illuminate\Contracts\Cache\Repository;
use Illuminate\Database\Eloquent\Relations\Relation;
+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 Schema;
-use URL;
class AppServiceProvider extends ServiceProvider
{
]);
// View Composers
- View::composer('partials.breadcrumbs', BreadcrumbsViewComposer::class);
+ View::composer('entities.breadcrumbs', BreadcrumbsViewComposer::class);
}
/**
});
$this->app->singleton(SocialAuthService::class, function ($app) {
- return new SocialAuthService($app->make(SocialiteFactory::class));
+ return new SocialAuthService($app->make(SocialiteFactory::class), $app->make(LoginService::class));
+ });
+
+ $this->app->singleton(CspService::class, function ($app) {
+ return new CspService();
});
}
}
namespace BookStack\Providers;
-use Auth;
use BookStack\Api\ApiTokenGuard;
use BookStack\Auth\Access\ExternalBaseUserProvider;
use BookStack\Auth\Access\Guards\LdapSessionGuard;
use BookStack\Auth\Access\Guards\Saml2SessionGuard;
use BookStack\Auth\Access\LdapService;
+use BookStack\Auth\Access\LoginService;
use BookStack\Auth\Access\RegistrationService;
+use Illuminate\Support\Facades\Auth;
use Illuminate\Support\ServiceProvider;
class AuthServiceProvider extends ServiceProvider
public function boot()
{
Auth::extend('api-token', function ($app, $name, array $config) {
- return new ApiTokenGuard($app['request']);
+ return new ApiTokenGuard($app['request'], $app->make(LoginService::class));
});
Auth::extend('ldap-session', function ($app, $name, array $config) {
return new LdapSessionGuard(
$name,
$provider,
- $this->app['session.store'],
+ $app['session.store'],
$app[LdapService::class],
$app[RegistrationService::class]
);
return new Saml2SessionGuard(
$name,
$provider,
- $this->app['session.store'],
+ $app['session.store'],
$app[RegistrationService::class]
);
});
namespace BookStack\Providers;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
-use Route;
+use Illuminate\Support\Facades\Route;
class RouteServiceProvider extends ServiceProvider
{
--- /dev/null
+<?php
+
+namespace BookStack\Theming;
+
+use BookStack\Util\CspService;
+use BookStack\Util\HtmlContentFilter;
+use BookStack\Util\HtmlNonceApplicator;
+use Illuminate\Contracts\Cache\Repository as Cache;
+
+class CustomHtmlHeadContentProvider
+{
+ /**
+ * @var CspService
+ */
+ protected $cspService;
+
+ /**
+ * @var Cache
+ */
+ protected $cache;
+
+ public function __construct(CspService $cspService, Cache $cache)
+ {
+ $this->cspService = $cspService;
+ $this->cache = $cache;
+ }
+
+ /**
+ * Fetch our custom HTML head content prepared for use on web pages.
+ * Content has a nonce applied for CSP.
+ */
+ public function forWeb(): string
+ {
+ $content = $this->getSourceContent();
+ $hash = md5($content);
+ $html = $this->cache->remember('custom-head-web:' . $hash, 86400, function () use ($content) {
+ return HtmlNonceApplicator::prepare($content);
+ });
+
+ return HtmlNonceApplicator::apply($html, $this->cspService->getNonce());
+ }
+
+ /**
+ * Fetch our custom HTML head content prepared for use in export formats.
+ * Scripts are stripped to avoid potential issues.
+ */
+ public function forExport(): string
+ {
+ $content = $this->getSourceContent();
+ $hash = md5($content);
+
+ return $this->cache->remember('custom-head-export:' . $hash, 86400, function () use ($content) {
+ return HtmlContentFilter::removeScripts($content);
+ });
+ }
+
+ /**
+ * Get the original custom head content to use.
+ */
+ protected function getSourceContent(): string
+ {
+ return setting('app-custom-head', '');
+ }
+}
* Provides both the original request and the currently resolved response.
* Return values, if provided, will be used as a new response to use.
*
- * @param \Illuminate\Http\Request $request
+ * @param \Illuminate\Http\Request $request
* @param \Illuminate\Http\Response|Symfony\Component\HttpFoundation\BinaryFileResponse $response
* @returns \Illuminate\Http\Response|null
*/
if (is_null($namespace) || $namespace === '*') {
$themePath = theme_path('lang');
$themeTranslations = $themePath ? $this->loadPath($themePath, $locale, $group) : [];
- $originalTranslations = $this->loadPath($this->path, $locale, $group);
+ $originalTranslations = $this->loadPath($this->path, $locale, $group);
+
return array_merge($originalTranslations, $themeTranslations);
}
use Illuminate\Contracts\Filesystem\Factory as FileSystem;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Illuminate\Contracts\Filesystem\Filesystem as FileSystemInstance;
+use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
-use Log;
use Symfony\Component\HttpFoundation\File\UploadedFile;
class AttachmentService
namespace BookStack\Uploads;
use BookStack\Exceptions\ImageUploadException;
-use DB;
use ErrorException;
use Exception;
use Illuminate\Contracts\Cache\Repository as Cache;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Illuminate\Contracts\Filesystem\Filesystem as FileSystemInstance;
use Illuminate\Contracts\Filesystem\Filesystem as Storage;
+use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Intervention\Image\Exception\NotSupportedException;
use Intervention\Image\ImageManager;
--- /dev/null
+<?php
+
+namespace BookStack\Util;
+
+use Illuminate\Support\Str;
+use Symfony\Component\HttpFoundation\Response;
+
+class CspService
+{
+ /** @var string */
+ protected $nonce;
+
+ public function __construct(string $nonce = '')
+ {
+ $this->nonce = $nonce ?: Str::random(24);
+ }
+
+ /**
+ * Get the nonce value for CSP.
+ */
+ public function getNonce(): string
+ {
+ return $this->nonce;
+ }
+
+ /**
+ * Sets CSP 'script-src' headers to restrict the forms of script that can
+ * run on the page.
+ */
+ public function setScriptSrc(Response $response)
+ {
+ if (config('app.allow_content_scripts')) {
+ return;
+ }
+
+ $parts = [
+ 'http:',
+ 'https:',
+ '\'nonce-' . $this->nonce . '\'',
+ '\'strict-dynamic\'',
+ ];
+
+ $value = 'script-src ' . implode(' ', $parts);
+ $response->headers->set('Content-Security-Policy', $value, false);
+ }
+
+ /**
+ * Sets CSP "frame-ancestors" headers to restrict the hosts that BookStack can be
+ * iframed within. Also adjusts the cookie samesite options so that cookies will
+ * operate in the third-party context.
+ */
+ public function setFrameAncestors(Response $response)
+ {
+ $iframeHosts = $this->getAllowedIframeHosts();
+ array_unshift($iframeHosts, "'self'");
+ $cspValue = 'frame-ancestors ' . implode(' ', $iframeHosts);
+ $response->headers->set('Content-Security-Policy', $cspValue, false);
+ }
+
+ /**
+ * Check if the user has configured some allowed iframe hosts.
+ */
+ public function allowedIFrameHostsConfigured(): bool
+ {
+ return count($this->getAllowedIframeHosts()) > 0;
+ }
+
+ /**
+ * Sets CSP 'object-src' headers to restrict the types of dynamic content
+ * that can be embedded on the page.
+ */
+ public function setObjectSrc(Response $response)
+ {
+ if (config('app.allow_content_scripts')) {
+ return;
+ }
+
+ $response->headers->set('Content-Security-Policy', 'object-src \'self\'', false);
+ }
+
+ /**
+ * Sets CSP 'base-uri' headers to restrict what base tags can be set on
+ * the page to prevent manipulation of relative links.
+ */
+ public function setBaseUri(Response $response)
+ {
+ $response->headers->set('Content-Security-Policy', 'base-uri \'self\'', false);
+ }
+
+ protected function getAllowedIframeHosts(): array
+ {
+ $hosts = config('app.iframe_hosts', '');
+
+ return array_filter(explode(' ', $hosts));
+ }
+}
namespace BookStack\Util;
+use DOMAttr;
use DOMDocument;
use DOMNodeList;
use DOMXPath;
class HtmlContentFilter
{
/**
- * Remove all of the script elements from the given HTML.
+ * Remove all the script elements from the given HTML.
*/
public static function removeScripts(string $html): string
{
static::removeNodes($scriptElems);
// Remove clickable links to JavaScript URI
- $badLinks = $xPath->query('//*[contains(@href, \'javascript:\')]');
+ $badLinks = $xPath->query('//*[' . static::xpathContains('@href', 'javascript:') . ']');
static::removeNodes($badLinks);
// Remove forms with calls to JavaScript URI
- $badForms = $xPath->query('//*[contains(@action, \'javascript:\')] | //*[contains(@formaction, \'javascript:\')]');
+ $badForms = $xPath->query('//*[' . static::xpathContains('@action', 'javascript:') . '] | //*[' . static::xpathContains('@formaction', 'javascript:') . ']');
static::removeNodes($badForms);
// Remove meta tag to prevent external redirects
- $metaTags = $xPath->query('//meta[contains(@content, \'url\')]');
+ $metaTags = $xPath->query('//meta[' . static::xpathContains('@content', 'url') . ']');
static::removeNodes($metaTags);
// Remove data or JavaScript iFrames
- $badIframes = $xPath->query('//*[contains(@src, \'data:\')] | //*[contains(@src, \'javascript:\')] | //*[@srcdoc]');
+ $badIframes = $xPath->query('//*[' . static::xpathContains('@src', 'data:') . '] | //*[' . static::xpathContains('@src', 'javascript:') . '] | //*[@srcdoc]');
static::removeNodes($badIframes);
+ // Remove elements with a xlink:href attribute
+ // Used in SVG but deprecated anyway, so we'll be a bit more heavy-handed here.
+ $xlinkHrefAttributes = $xPath->query('//@*[contains(name(), \'xlink:href\')]');
+ static::removeAttributes($xlinkHrefAttributes);
+
// Remove 'on*' attributes
$onAttributes = $xPath->query('//@*[starts-with(name(), \'on\')]');
- foreach ($onAttributes as $attr) {
- /** @var \DOMAttr $attr */
- $attrName = $attr->nodeName;
- $attr->parentNode->removeAttribute($attrName);
- }
+ static::removeAttributes($onAttributes);
$html = '';
$topElems = $doc->documentElement->childNodes->item(0)->childNodes;
}
/**
- * Removed all of the given DOMNodes.
+ * Create a xpath contains statement with a translation automatically built within
+ * to affectively search in a cases-insensitive manner.
+ */
+ protected static function xpathContains(string $property, string $value): string
+ {
+ $value = strtolower($value);
+ $upperVal = strtoupper($value);
+
+ return 'contains(translate(' . $property . ', \'' . $upperVal . '\', \'' . $value . '\'), \'' . $value . '\')';
+ }
+
+ /**
+ * Remove all the given DOMNodes.
*/
protected static function removeNodes(DOMNodeList $nodes): void
{
$node->parentNode->removeChild($node);
}
}
+
+ /**
+ * Remove all the given attribute nodes.
+ */
+ protected static function removeAttributes(DOMNodeList $attrs): void
+ {
+ /** @var DOMAttr $attr */
+ foreach ($attrs as $attr) {
+ $attrName = $attr->nodeName;
+ $attr->parentNode->removeAttribute($attrName);
+ }
+ }
}
--- /dev/null
+<?php
+
+namespace BookStack\Util;
+
+use DOMDocument;
+use DOMElement;
+use DOMNodeList;
+use DOMXPath;
+
+class HtmlNonceApplicator
+{
+ protected static $placeholder = '[CSP_NONCE_VALUE]';
+
+ /**
+ * Prepare the given HTML content with nonce attributes including a placeholder
+ * value which we can target later.
+ */
+ public static function prepare(string $html): string
+ {
+ if (empty($html)) {
+ return $html;
+ }
+
+ $html = '<?xml encoding="utf-8" ?><body>' . $html . '</body>';
+ libxml_use_internal_errors(true);
+ $doc = new DOMDocument();
+ $doc->loadHTML($html, LIBXML_SCHEMA_CREATE);
+ $xPath = new DOMXPath($doc);
+
+ // Apply to scripts
+ $scriptElems = $xPath->query('//script');
+ static::addNonceAttributes($scriptElems, static::$placeholder);
+
+ // Apply to styles
+ $styleElems = $xPath->query('//style');
+ static::addNonceAttributes($styleElems, static::$placeholder);
+
+ $returnHtml = '';
+ $topElems = $doc->documentElement->childNodes->item(0)->childNodes;
+ foreach ($topElems as $child) {
+ $content = $doc->saveHTML($child);
+ $returnHtml .= $content;
+ }
+
+ return $returnHtml;
+ }
+
+ /**
+ * Apply the give nonce value to the given prepared HTML.
+ */
+ public static function apply(string $html, string $nonce): string
+ {
+ return str_replace(static::$placeholder, $nonce, $html);
+ }
+
+ protected static function addNonceAttributes(DOMNodeList $nodes, string $attrValue): void
+ {
+ /** @var DOMElement $node */
+ foreach ($nodes as $node) {
+ $node->setAttribute('nonce', $attrValue);
+ }
+ }
+}
"ext-json": "*",
"ext-mbstring": "*",
"ext-xml": "*",
+ "bacon/bacon-qr-code": "^2.0",
"barryvdh/laravel-dompdf": "^0.9.0",
"barryvdh/laravel-snappy": "^0.4.8",
"doctrine/dbal": "^2.12.1",
"facade/ignition": "^1.16.4",
"fideloper/proxy": "^4.4.1",
"intervention/image": "^2.5.1",
- "laravel/framework": "^6.20.16",
+ "laravel/framework": "^6.20.33",
"laravel/socialite": "^5.1",
"league/commonmark": "^1.5",
"league/flysystem-aws-s3-v3": "^1.0.29",
"league/html-to-markdown": "^5.0.0",
"nunomaduro/collision": "^3.1",
"onelogin/php-saml": "^4.0",
+ "pragmarx/google2fa": "^8.0",
"predis/predis": "^1.1.6",
"socialiteproviders/discord": "^4.1",
"socialiteproviders/gitlab": "^4.1",
"barryvdh/laravel-debugbar": "^3.5.1",
"barryvdh/laravel-ide-helper": "^2.8.2",
"fakerphp/faker": "^1.13.0",
- "laravel/browser-kit-testing": "^5.2",
"mockery/mockery": "^1.3.3",
- "phpunit/phpunit": "^9.5.3"
+ "phpunit/phpunit": "^9.5.3",
+ "symfony/dom-crawler": "^5.3"
},
"autoload": {
"classmap": [
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "d1109d0dc4a6ab525cdbf64ed21f6dd4",
+ "content-hash": "10825887b8f66d1d412b92bcc0ca864f",
"packages": [
+ {
+ "name": "aws/aws-crt-php",
+ "version": "v1.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/awslabs/aws-crt-php.git",
+ "reference": "3942776a8c99209908ee0b287746263725685732"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/3942776a8c99209908ee0b287746263725685732",
+ "reference": "3942776a8c99209908ee0b287746263725685732",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.8.35|^5.4.3"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "AWS SDK Common Runtime Team",
+ }
+ ],
+ "description": "AWS Common Runtime for PHP",
+ "homepage": "http://aws.amazon.com/sdkforphp",
+ "keywords": [
+ "amazon",
+ "aws",
+ "crt",
+ "sdk"
+ ],
+ "support": {
+ "issues": "https://github.com/awslabs/aws-crt-php/issues",
+ "source": "https://github.com/awslabs/aws-crt-php/tree/v1.0.2"
+ },
+ "time": "2021-09-03T22:57:30+00:00"
+ },
{
"name": "aws/aws-sdk-php",
- "version": "3.187.2",
+ "version": "3.194.1",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
- "reference": "0ec4ae120cfae758efa3c283dc56eb20602f094c"
+ "reference": "67bdee05acef9e8ad60098090996690b49babd09"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/0ec4ae120cfae758efa3c283dc56eb20602f094c",
- "reference": "0ec4ae120cfae758efa3c283dc56eb20602f094c",
+ "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/67bdee05acef9e8ad60098090996690b49babd09",
+ "reference": "67bdee05acef9e8ad60098090996690b49babd09",
"shasum": ""
},
"require": {
+ "aws/aws-crt-php": "^1.0.2",
"ext-json": "*",
"ext-pcre": "*",
"ext-simplexml": "*",
"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.187.2"
+ "source": "https://github.com/aws/aws-sdk-php/tree/3.194.1"
+ },
+ "time": "2021-09-17T18:15:42+00:00"
+ },
+ {
+ "name": "bacon/bacon-qr-code",
+ "version": "2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Bacon/BaconQrCode.git",
+ "reference": "f73543ac4e1def05f1a70bcd1525c8a157a1ad09"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/f73543ac4e1def05f1a70bcd1525c8a157a1ad09",
+ "reference": "f73543ac4e1def05f1a70bcd1525c8a157a1ad09",
+ "shasum": ""
+ },
+ "require": {
+ "dasprid/enum": "^1.0.3",
+ "ext-iconv": "*",
+ "php": "^7.1 || ^8.0"
+ },
+ "require-dev": {
+ "phly/keep-a-changelog": "^1.4",
+ "phpunit/phpunit": "^7 | ^8 | ^9",
+ "squizlabs/php_codesniffer": "^3.4"
+ },
+ "suggest": {
+ "ext-imagick": "to generate QR code images"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "BaconQrCode\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-2-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Ben Scholzen 'DASPRiD'",
+ "homepage": "https://dasprids.de/",
+ "role": "Developer"
+ }
+ ],
+ "description": "BaconQrCode is a QR code generator for PHP.",
+ "homepage": "https://github.com/Bacon/BaconQrCode",
+ "support": {
+ "issues": "https://github.com/Bacon/BaconQrCode/issues",
+ "source": "https://github.com/Bacon/BaconQrCode/tree/2.0.4"
},
- "time": "2021-08-04T18:12:21+00:00"
+ "time": "2021-06-18T13:26:35+00:00"
},
{
"name": "barryvdh/laravel-dompdf",
},
"time": "2020-09-07T12:33:10+00:00"
},
+ {
+ "name": "dasprid/enum",
+ "version": "1.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/DASPRiD/Enum.git",
+ "reference": "5abf82f213618696dda8e3bf6f64dd042d8542b2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/5abf82f213618696dda8e3bf6f64dd042d8542b2",
+ "reference": "5abf82f213618696dda8e3bf6f64dd042d8542b2",
+ "shasum": ""
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^7 | ^8 | ^9",
+ "squizlabs/php_codesniffer": "^3.4"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "DASPRiD\\Enum\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-2-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Ben Scholzen 'DASPRiD'",
+ "homepage": "https://dasprids.de/",
+ "role": "Developer"
+ }
+ ],
+ "description": "PHP 7.1 enum implementation",
+ "keywords": [
+ "enum",
+ "map"
+ ],
+ "support": {
+ "issues": "https://github.com/DASPRiD/Enum/issues",
+ "source": "https://github.com/DASPRiD/Enum/tree/1.0.3"
+ },
+ "time": "2020-10-02T16:03:48+00:00"
+ },
{
"name": "doctrine/cache",
"version": "2.1.1",
},
{
"name": "doctrine/dbal",
- "version": "2.13.2",
+ "version": "2.13.3",
"source": {
"type": "git",
"url": "https://github.com/doctrine/dbal.git",
- "reference": "8dd39d2ead4409ce652fd4f02621060f009ea5e4"
+ "reference": "0d7adf4cadfee6f70850e5b163e6cdd706417838"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/doctrine/dbal/zipball/8dd39d2ead4409ce652fd4f02621060f009ea5e4",
- "reference": "8dd39d2ead4409ce652fd4f02621060f009ea5e4",
+ "url": "https://api.github.com/repos/doctrine/dbal/zipball/0d7adf4cadfee6f70850e5b163e6cdd706417838",
+ "reference": "0d7adf4cadfee6f70850e5b163e6cdd706417838",
"shasum": ""
},
"require": {
},
"require-dev": {
"doctrine/coding-standard": "9.0.0",
- "jetbrains/phpstorm-stubs": "2020.2",
- "phpstan/phpstan": "0.12.81",
+ "jetbrains/phpstorm-stubs": "2021.1",
+ "phpstan/phpstan": "0.12.96",
"phpunit/phpunit": "^7.5.20|^8.5|9.5.5",
+ "psalm/plugin-phpunit": "0.16.1",
"squizlabs/php_codesniffer": "3.6.0",
"symfony/cache": "^4.4",
"symfony/console": "^2.0.5|^3.0|^4.0|^5.0",
- "vimeo/psalm": "4.6.4"
+ "vimeo/psalm": "4.10.0"
},
"suggest": {
"symfony/console": "For helpful console commands such as SQL execution and import of files."
],
"support": {
"issues": "https://github.com/doctrine/dbal/issues",
- "source": "https://github.com/doctrine/dbal/tree/2.13.2"
+ "source": "https://github.com/doctrine/dbal/tree/2.13.3"
},
"funding": [
{
"type": "tidelift"
}
],
- "time": "2021-06-18T21:48:39+00:00"
+ "time": "2021-09-12T19:11:48+00:00"
},
{
"name": "doctrine/deprecations",
},
{
"name": "facade/flare-client-php",
- "version": "1.8.1",
+ "version": "1.9.1",
"source": {
"type": "git",
"url": "https://github.com/facade/flare-client-php.git",
- "reference": "47b639dc02bcfdfc4ebb83de703856fa01e35f5f"
+ "reference": "b2adf1512755637d0cef4f7d1b54301325ac78ed"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/facade/flare-client-php/zipball/47b639dc02bcfdfc4ebb83de703856fa01e35f5f",
- "reference": "47b639dc02bcfdfc4ebb83de703856fa01e35f5f",
+ "url": "https://api.github.com/repos/facade/flare-client-php/zipball/b2adf1512755637d0cef4f7d1b54301325ac78ed",
+ "reference": "b2adf1512755637d0cef4f7d1b54301325ac78ed",
"shasum": ""
},
"require": {
],
"support": {
"issues": "https://github.com/facade/flare-client-php/issues",
- "source": "https://github.com/facade/flare-client-php/tree/1.8.1"
+ "source": "https://github.com/facade/flare-client-php/tree/1.9.1"
},
"funding": [
{
"type": "github"
}
],
- "time": "2021-05-31T19:23:29+00:00"
+ "time": "2021-09-13T12:16:46+00:00"
},
{
"name": "facade/ignition",
},
{
"name": "filp/whoops",
- "version": "2.14.0",
+ "version": "2.14.1",
"source": {
"type": "git",
"url": "https://github.com/filp/whoops.git",
- "reference": "fdf92f03e150ed84d5967a833ae93abffac0315b"
+ "reference": "15ead64e9828f0fc90932114429c4f7923570cb1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/filp/whoops/zipball/fdf92f03e150ed84d5967a833ae93abffac0315b",
- "reference": "fdf92f03e150ed84d5967a833ae93abffac0315b",
+ "url": "https://api.github.com/repos/filp/whoops/zipball/15ead64e9828f0fc90932114429c4f7923570cb1",
+ "reference": "15ead64e9828f0fc90932114429c4f7923570cb1",
"shasum": ""
},
"require": {
],
"support": {
"issues": "https://github.com/filp/whoops/issues",
- "source": "https://github.com/filp/whoops/tree/2.14.0"
+ "source": "https://github.com/filp/whoops/tree/2.14.1"
},
"funding": [
{
"type": "github"
}
],
- "time": "2021-07-13T12:00:00+00:00"
+ "time": "2021-08-29T12:00:00+00:00"
},
{
"name": "guzzlehttp/guzzle",
},
{
"name": "laravel/framework",
- "version": "v6.20.31",
+ "version": "v6.20.34",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
- "reference": "15cd96b48fb4f652ba996041cdb9a2a3b03e00f5"
+ "reference": "72a6da88c90cee793513b3fe49cf0fcb368eefa0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/framework/zipball/15cd96b48fb4f652ba996041cdb9a2a3b03e00f5",
- "reference": "15cd96b48fb4f652ba996041cdb9a2a3b03e00f5",
+ "url": "https://api.github.com/repos/laravel/framework/zipball/72a6da88c90cee793513b3fe49cf0fcb368eefa0",
+ "reference": "72a6da88c90cee793513b3fe49cf0fcb368eefa0",
"shasum": ""
},
"require": {
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
- "time": "2021-08-03T14:36:18+00:00"
+ "time": "2021-09-07T13:28:55+00:00"
},
{
"name": "laravel/socialite",
- "version": "v5.2.3",
+ "version": "v5.2.5",
"source": {
"type": "git",
"url": "https://github.com/laravel/socialite.git",
- "reference": "1960802068f81e44b2ae9793932181cf1cb91b5c"
+ "reference": "fd0f6a3dd963ca480b598649b54f92d81a43617f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/socialite/zipball/1960802068f81e44b2ae9793932181cf1cb91b5c",
- "reference": "1960802068f81e44b2ae9793932181cf1cb91b5c",
+ "url": "https://api.github.com/repos/laravel/socialite/zipball/fd0f6a3dd963ca480b598649b54f92d81a43617f",
+ "reference": "fd0f6a3dd963ca480b598649b54f92d81a43617f",
"shasum": ""
},
"require": {
"issues": "https://github.com/laravel/socialite/issues",
"source": "https://github.com/laravel/socialite"
},
- "time": "2021-04-06T14:38:16+00:00"
+ "time": "2021-08-31T15:16:26+00:00"
},
{
"name": "league/commonmark",
},
{
"name": "league/flysystem",
- "version": "1.1.4",
+ "version": "1.1.5",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem.git",
- "reference": "f3ad69181b8afed2c9edf7be5a2918144ff4ea32"
+ "reference": "18634df356bfd4119fe3d6156bdb990c414c14ea"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/f3ad69181b8afed2c9edf7be5a2918144ff4ea32",
- "reference": "f3ad69181b8afed2c9edf7be5a2918144ff4ea32",
+ "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/18634df356bfd4119fe3d6156bdb990c414c14ea",
+ "reference": "18634df356bfd4119fe3d6156bdb990c414c14ea",
"shasum": ""
},
"require": {
],
"support": {
"issues": "https://github.com/thephpleague/flysystem/issues",
- "source": "https://github.com/thephpleague/flysystem/tree/1.1.4"
+ "source": "https://github.com/thephpleague/flysystem/tree/1.1.5"
},
"funding": [
{
"type": "other"
}
],
- "time": "2021-06-23T21:56:05+00:00"
+ "time": "2021-08-17T13:49:42+00:00"
},
{
"name": "league/flysystem-aws-s3-v3",
},
{
"name": "league/html-to-markdown",
- "version": "5.0.0",
+ "version": "5.0.1",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/html-to-markdown.git",
- "reference": "c4dbebbebe0fe454b6b38e6c683a977615bd7dc2"
+ "reference": "e5600a2c5ce7b7571b16732c7086940f56f7abec"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/html-to-markdown/zipball/c4dbebbebe0fe454b6b38e6c683a977615bd7dc2",
- "reference": "c4dbebbebe0fe454b6b38e6c683a977615bd7dc2",
+ "url": "https://api.github.com/repos/thephpleague/html-to-markdown/zipball/e5600a2c5ce7b7571b16732c7086940f56f7abec",
+ "reference": "e5600a2c5ce7b7571b16732c7086940f56f7abec",
"shasum": ""
},
"require": {
],
"support": {
"issues": "https://github.com/thephpleague/html-to-markdown/issues",
- "source": "https://github.com/thephpleague/html-to-markdown/tree/5.0.0"
+ "source": "https://github.com/thephpleague/html-to-markdown/tree/5.0.1"
},
"funding": [
{
"type": "github"
},
{
- "url": "https://www.patreon.com/colinodell",
- "type": "patreon"
+ "url": "https://tidelift.com/funding/github/packagist/league/html-to-markdown",
+ "type": "tidelift"
}
],
- "time": "2021-03-29T01:29:08+00:00"
+ "time": "2021-09-17T20:00:27+00:00"
},
{
"name": "league/mime-type-detection",
},
{
"name": "league/oauth1-client",
- "version": "1.9.2",
+ "version": "v1.10.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/oauth1-client.git",
- "reference": "6247ffbf6b74fcc7f21313315b79e9b2560e68b0"
+ "reference": "88dd16b0cff68eb9167bfc849707d2c40ad91ddc"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/6247ffbf6b74fcc7f21313315b79e9b2560e68b0",
- "reference": "6247ffbf6b74fcc7f21313315b79e9b2560e68b0",
+ "url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/88dd16b0cff68eb9167bfc849707d2c40ad91ddc",
+ "reference": "88dd16b0cff68eb9167bfc849707d2c40ad91ddc",
"shasum": ""
},
"require": {
],
"support": {
"issues": "https://github.com/thephpleague/oauth1-client/issues",
- "source": "https://github.com/thephpleague/oauth1-client/tree/1.9.2"
+ "source": "https://github.com/thephpleague/oauth1-client/tree/v1.10.0"
},
- "time": "2021-08-03T23:29:01+00:00"
+ "time": "2021-08-15T23:05:49+00:00"
},
{
"name": "monolog/monolog",
- "version": "2.3.2",
+ "version": "2.3.4",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/monolog.git",
- "reference": "71312564759a7db5b789296369c1a264efc43aad"
+ "reference": "437e7a1c50044b92773b361af77620efb76fff59"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Seldaek/monolog/zipball/71312564759a7db5b789296369c1a264efc43aad",
- "reference": "71312564759a7db5b789296369c1a264efc43aad",
+ "url": "https://api.github.com/repos/Seldaek/monolog/zipball/437e7a1c50044b92773b361af77620efb76fff59",
+ "reference": "437e7a1c50044b92773b361af77620efb76fff59",
"shasum": ""
},
"require": {
"php": ">=7.2",
- "psr/log": "^1.0.1"
+ "psr/log": "^1.0.1 || ^2.0 || ^3.0"
},
"provide": {
- "psr/log-implementation": "1.0.0"
+ "psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0"
},
"require-dev": {
"aws/aws-sdk-php": "^2.4.9 || ^3.0",
"phpunit/phpunit": "^8.5",
"predis/predis": "^1.1",
"rollbar/rollbar": "^1.3",
- "ruflin/elastica": ">=0.90 <7.0.1",
+ "ruflin/elastica": ">=0.90@dev",
"swiftmailer/swiftmailer": "^5.3|^6.0"
},
"suggest": {
"doctrine/couchdb": "Allow sending log messages to a CouchDB server",
"elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client",
"ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
+ "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler",
"ext-mbstring": "Allow to work properly with unicode symbols",
"ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)",
+ "ext-openssl": "Required to send log messages using SSL",
+ "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)",
"graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
"mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)",
"php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
],
"support": {
"issues": "https://github.com/Seldaek/monolog/issues",
- "source": "https://github.com/Seldaek/monolog/tree/2.3.2"
+ "source": "https://github.com/Seldaek/monolog/tree/2.3.4"
},
"funding": [
{
"type": "tidelift"
}
],
- "time": "2021-07-23T07:42:52+00:00"
+ "time": "2021-09-15T11:27:21+00:00"
},
{
"name": "mtdowling/jmespath.php",
},
{
"name": "nesbot/carbon",
- "version": "2.51.1",
+ "version": "2.53.1",
"source": {
"type": "git",
"url": "https://github.com/briannesbitt/Carbon.git",
- "reference": "8619c299d1e0d4b344e1f98ca07a1ce2cfbf1922"
+ "reference": "f4655858a784988f880c1b8c7feabbf02dfdf045"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/8619c299d1e0d4b344e1f98ca07a1ce2cfbf1922",
- "reference": "8619c299d1e0d4b344e1f98ca07a1ce2cfbf1922",
+ "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/f4655858a784988f880c1b8c7feabbf02dfdf045",
+ "reference": "f4655858a784988f880c1b8c7feabbf02dfdf045",
"shasum": ""
},
"require": {
},
"require-dev": {
"doctrine/orm": "^2.7",
- "friendsofphp/php-cs-fixer": "^2.14 || ^3.0",
+ "friendsofphp/php-cs-fixer": "^3.0",
"kylekatarnls/multi-tester": "^2.0",
"phpmd/phpmd": "^2.9",
"phpstan/extension-installer": "^1.0",
"type": "tidelift"
}
],
- "time": "2021-07-28T13:16:28+00:00"
+ "time": "2021-09-06T09:29:23+00:00"
},
{
"name": "nunomaduro/collision",
},
"time": "2021-04-09T13:42:10+00:00"
},
+ {
+ "name": "paragonie/constant_time_encoding",
+ "version": "v2.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/paragonie/constant_time_encoding.git",
+ "reference": "f34c2b11eb9d2c9318e13540a1dbc2a3afbd939c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/f34c2b11eb9d2c9318e13540a1dbc2a3afbd939c",
+ "reference": "f34c2b11eb9d2c9318e13540a1dbc2a3afbd939c",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7|^8"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6|^7|^8|^9",
+ "vimeo/psalm": "^1|^2|^3|^4"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "ParagonIE\\ConstantTime\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Paragon Initiative Enterprises",
+ "homepage": "https://paragonie.com",
+ "role": "Maintainer"
+ },
+ {
+ "name": "Steve 'Sc00bz' Thomas",
+ "homepage": "https://www.tobtu.com",
+ "role": "Original Developer"
+ }
+ ],
+ "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)",
+ "keywords": [
+ "base16",
+ "base32",
+ "base32_decode",
+ "base32_encode",
+ "base64",
+ "base64_decode",
+ "base64_encode",
+ "bin2hex",
+ "encoding",
+ "hex",
+ "hex2bin",
+ "rfc4648"
+ ],
+ "support": {
+ "issues": "https://github.com/paragonie/constant_time_encoding/issues",
+ "source": "https://github.com/paragonie/constant_time_encoding"
+ },
+ "time": "2020-12-06T15:14:20+00:00"
+ },
{
"name": "paragonie/random_compat",
- "version": "v9.99.99",
+ "version": "v9.99.100",
"source": {
"type": "git",
"url": "https://github.com/paragonie/random_compat.git",
- "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95"
+ "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95",
- "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95",
+ "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a",
+ "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a",
"shasum": ""
},
"require": {
- "php": "^7"
+ "php": ">= 7"
},
"require-dev": {
"phpunit/phpunit": "4.*|5.*",
"issues": "https://github.com/paragonie/random_compat/issues",
"source": "https://github.com/paragonie/random_compat"
},
- "time": "2018-07-02T15:55:56+00:00"
+ "time": "2020-10-15T08:29:30+00:00"
},
{
"name": "phenx/php-font-lib",
},
{
"name": "phpoption/phpoption",
- "version": "1.7.5",
+ "version": "1.8.0",
"source": {
"type": "git",
"url": "https://github.com/schmittjoh/php-option.git",
- "reference": "994ecccd8f3283ecf5ac33254543eb0ac946d525"
+ "reference": "5455cb38aed4523f99977c4a12ef19da4bfe2a28"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/994ecccd8f3283ecf5ac33254543eb0ac946d525",
- "reference": "994ecccd8f3283ecf5ac33254543eb0ac946d525",
+ "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/5455cb38aed4523f99977c4a12ef19da4bfe2a28",
+ "reference": "5455cb38aed4523f99977c4a12ef19da4bfe2a28",
"shasum": ""
},
"require": {
- "php": "^5.5.9 || ^7.0 || ^8.0"
+ "php": "^7.0 || ^8.0"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.4.1",
- "phpunit/phpunit": "^4.8.35 || ^5.7.27 || ^6.5.6 || ^7.0 || ^8.0 || ^9.0"
+ "phpunit/phpunit": "^6.5.14 || ^7.0.20 || ^8.5.19 || ^9.5.8"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.7-dev"
+ "dev-master": "1.8-dev"
}
},
"autoload": {
},
{
"name": "Graham Campbell",
}
],
"description": "Option Type for PHP",
],
"support": {
"issues": "https://github.com/schmittjoh/php-option/issues",
- "source": "https://github.com/schmittjoh/php-option/tree/1.7.5"
+ "source": "https://github.com/schmittjoh/php-option/tree/1.8.0"
},
"funding": [
{
"type": "tidelift"
}
],
- "time": "2020-07-20T17:29:33+00:00"
+ "time": "2021-08-28T21:27:29+00:00"
+ },
+ {
+ "name": "pragmarx/google2fa",
+ "version": "8.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/antonioribeiro/google2fa.git",
+ "reference": "26c4c5cf30a2844ba121760fd7301f8ad240100b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/26c4c5cf30a2844ba121760fd7301f8ad240100b",
+ "reference": "26c4c5cf30a2844ba121760fd7301f8ad240100b",
+ "shasum": ""
+ },
+ "require": {
+ "paragonie/constant_time_encoding": "^1.0|^2.0",
+ "php": "^7.1|^8.0"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^0.12.18",
+ "phpunit/phpunit": "^7.5.15|^8.5|^9.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "PragmaRX\\Google2FA\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Antonio Carlos Ribeiro",
+ "role": "Creator & Designer"
+ }
+ ],
+ "description": "A One Time Password Authentication package, compatible with Google Authenticator.",
+ "keywords": [
+ "2fa",
+ "Authentication",
+ "Two Factor Authentication",
+ "google2fa"
+ ],
+ "support": {
+ "issues": "https://github.com/antonioribeiro/google2fa/issues",
+ "source": "https://github.com/antonioribeiro/google2fa/tree/8.0.0"
+ },
+ "time": "2020-04-05T10:47:18+00:00"
},
{
"name": "predis/predis",
},
{
"name": "ramsey/uuid",
- "version": "3.9.3",
+ "version": "3.9.4",
"source": {
"type": "git",
"url": "https://github.com/ramsey/uuid.git",
- "reference": "7e1633a6964b48589b142d60542f9ed31bd37a92"
+ "reference": "be2451bef8147b7352a28fb4cddb08adc497ada3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/ramsey/uuid/zipball/7e1633a6964b48589b142d60542f9ed31bd37a92",
- "reference": "7e1633a6964b48589b142d60542f9ed31bd37a92",
+ "url": "https://api.github.com/repos/ramsey/uuid/zipball/be2451bef8147b7352a28fb4cddb08adc497ada3",
+ "reference": "be2451bef8147b7352a28fb4cddb08adc497ada3",
"shasum": ""
},
"require": {
"ext-json": "*",
- "paragonie/random_compat": "^1 | ^2 | 9.99.99",
+ "paragonie/random_compat": "^1 | ^2 | ^9.99.99",
"php": "^5.4 | ^7 | ^8",
"symfony/polyfill-ctype": "^1.8"
},
"source": "https://github.com/ramsey/uuid",
"wiki": "https://github.com/ramsey/uuid/wiki"
},
- "time": "2020-02-21T04:36:14+00:00"
+ "funding": [
+ {
+ "url": "https://github.com/ramsey",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-08-06T20:32:15+00:00"
},
{
"name": "robrichards/xmlseclibs",
},
{
"name": "symfony/console",
- "version": "v4.4.29",
+ "version": "v4.4.30",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "8baf0bbcfddfde7d7225ae8e04705cfd1081cd7b"
+ "reference": "a3f7189a0665ee33b50e9e228c46f50f5acbed22"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/8baf0bbcfddfde7d7225ae8e04705cfd1081cd7b",
- "reference": "8baf0bbcfddfde7d7225ae8e04705cfd1081cd7b",
+ "url": "https://api.github.com/repos/symfony/console/zipball/a3f7189a0665ee33b50e9e228c46f50f5acbed22",
+ "reference": "a3f7189a0665ee33b50e9e228c46f50f5acbed22",
"shasum": ""
},
"require": {
"description": "Eases the creation of beautiful and testable command line interfaces",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/console/tree/v4.4.29"
+ "source": "https://github.com/symfony/console/tree/v4.4.30"
},
"funding": [
{
"type": "tidelift"
}
],
- "time": "2021-07-27T19:04:53+00:00"
+ "time": "2021-08-25T19:27:26+00:00"
},
{
"name": "symfony/css-selector",
- "version": "v4.4.27",
+ "version": "v5.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
- "reference": "5194f18bd80d106f11efa8f7cd0fbdcc3af96ce6"
+ "reference": "7fb120adc7f600a59027775b224c13a33530dd90"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/css-selector/zipball/5194f18bd80d106f11efa8f7cd0fbdcc3af96ce6",
- "reference": "5194f18bd80d106f11efa8f7cd0fbdcc3af96ce6",
+ "url": "https://api.github.com/repos/symfony/css-selector/zipball/7fb120adc7f600a59027775b224c13a33530dd90",
+ "reference": "7fb120adc7f600a59027775b224c13a33530dd90",
"shasum": ""
},
"require": {
- "php": ">=7.1.3",
+ "php": ">=7.2.5",
"symfony/polyfill-php80": "^1.16"
},
"type": "library",
"description": "Converts CSS selectors to XPath expressions",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/css-selector/tree/v4.4.27"
+ "source": "https://github.com/symfony/css-selector/tree/v5.3.4"
},
"funding": [
{
"type": "tidelift"
}
],
- "time": "2021-07-21T12:19:41+00:00"
+ "time": "2021-07-21T12:38:00+00:00"
},
{
"name": "symfony/debug",
},
{
"name": "symfony/error-handler",
- "version": "v4.4.27",
+ "version": "v4.4.30",
"source": {
"type": "git",
"url": "https://github.com/symfony/error-handler.git",
- "reference": "16ac2be1c0f49d6d9eb9d3ce9324bde268717905"
+ "reference": "51f98f7aa99f00f3b1da6bafe934e67ae6ba6dc5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/error-handler/zipball/16ac2be1c0f49d6d9eb9d3ce9324bde268717905",
- "reference": "16ac2be1c0f49d6d9eb9d3ce9324bde268717905",
+ "url": "https://api.github.com/repos/symfony/error-handler/zipball/51f98f7aa99f00f3b1da6bafe934e67ae6ba6dc5",
+ "reference": "51f98f7aa99f00f3b1da6bafe934e67ae6ba6dc5",
"shasum": ""
},
"require": {
"description": "Provides tools to manage errors and ease debugging PHP code",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/error-handler/tree/v4.4.27"
+ "source": "https://github.com/symfony/error-handler/tree/v4.4.30"
},
"funding": [
{
"type": "tidelift"
}
],
- "time": "2021-07-23T15:41:52+00:00"
+ "time": "2021-08-27T17:42:48+00:00"
},
{
"name": "symfony/event-dispatcher",
- "version": "v4.4.27",
+ "version": "v4.4.30",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
- "reference": "958a128b184fcf0ba45ec90c0e88554c9327c2e9"
+ "reference": "2fe81680070043c4c80e7cedceb797e34f377bac"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/958a128b184fcf0ba45ec90c0e88554c9327c2e9",
- "reference": "958a128b184fcf0ba45ec90c0e88554c9327c2e9",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/2fe81680070043c4c80e7cedceb797e34f377bac",
+ "reference": "2fe81680070043c4c80e7cedceb797e34f377bac",
"shasum": ""
},
"require": {
"description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/event-dispatcher/tree/v4.4.27"
+ "source": "https://github.com/symfony/event-dispatcher/tree/v4.4.30"
},
"funding": [
{
"type": "tidelift"
}
],
- "time": "2021-07-23T15:41:52+00:00"
+ "time": "2021-08-04T20:31:23+00:00"
},
{
"name": "symfony/event-dispatcher-contracts",
},
{
"name": "symfony/finder",
- "version": "v4.4.27",
+ "version": "v4.4.30",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
- "reference": "42414d7ac96fc2880a783b872185789dea0d4262"
+ "reference": "70362f1e112280d75b30087c7598b837c1b468b6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/finder/zipball/42414d7ac96fc2880a783b872185789dea0d4262",
- "reference": "42414d7ac96fc2880a783b872185789dea0d4262",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/70362f1e112280d75b30087c7598b837c1b468b6",
+ "reference": "70362f1e112280d75b30087c7598b837c1b468b6",
"shasum": ""
},
"require": {
"description": "Finds files and directories via an intuitive fluent interface",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/finder/tree/v4.4.27"
+ "source": "https://github.com/symfony/finder/tree/v4.4.30"
},
"funding": [
{
"type": "tidelift"
}
],
- "time": "2021-07-23T15:41:52+00:00"
+ "time": "2021-08-04T20:31:23+00:00"
},
{
"name": "symfony/http-client-contracts",
},
{
"name": "symfony/http-foundation",
- "version": "v4.4.29",
+ "version": "v4.4.30",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
- "reference": "7016057b01f0ed3ec3ba1f31a580b6661667c2e1"
+ "reference": "09b3202651ab23ac8dcf455284a48a3500e56731"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-foundation/zipball/7016057b01f0ed3ec3ba1f31a580b6661667c2e1",
- "reference": "7016057b01f0ed3ec3ba1f31a580b6661667c2e1",
+ "url": "https://api.github.com/repos/symfony/http-foundation/zipball/09b3202651ab23ac8dcf455284a48a3500e56731",
+ "reference": "09b3202651ab23ac8dcf455284a48a3500e56731",
"shasum": ""
},
"require": {
"description": "Defines an object-oriented layer for the HTTP specification",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/http-foundation/tree/v4.4.29"
+ "source": "https://github.com/symfony/http-foundation/tree/v4.4.30"
},
"funding": [
{
"type": "tidelift"
}
],
- "time": "2021-07-27T14:32:23+00:00"
+ "time": "2021-08-26T15:51:23+00:00"
},
{
"name": "symfony/http-kernel",
- "version": "v4.4.29",
+ "version": "v4.4.30",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
- "reference": "752b170e1ba0dd4104e7fa17c1cef1ec8a7fc506"
+ "reference": "87f7ea4a8a7a30c967e26001de99f12943bf57ae"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-kernel/zipball/752b170e1ba0dd4104e7fa17c1cef1ec8a7fc506",
- "reference": "752b170e1ba0dd4104e7fa17c1cef1ec8a7fc506",
+ "url": "https://api.github.com/repos/symfony/http-kernel/zipball/87f7ea4a8a7a30c967e26001de99f12943bf57ae",
+ "reference": "87f7ea4a8a7a30c967e26001de99f12943bf57ae",
"shasum": ""
},
"require": {
"symfony/error-handler": "^4.4",
"symfony/event-dispatcher": "^4.4",
"symfony/http-client-contracts": "^1.1|^2",
- "symfony/http-foundation": "^4.4|^5.0",
+ "symfony/http-foundation": "^4.4.30|^5.3.7",
"symfony/polyfill-ctype": "^1.8",
"symfony/polyfill-php73": "^1.9",
"symfony/polyfill-php80": "^1.16"
"description": "Provides a structured process for converting a Request into a Response",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/http-kernel/tree/v4.4.29"
+ "source": "https://github.com/symfony/http-kernel/tree/v4.4.30"
},
"funding": [
{
"type": "tidelift"
}
],
- "time": "2021-07-29T06:45:05+00:00"
+ "time": "2021-08-30T12:27:20+00:00"
},
{
"name": "symfony/mime",
- "version": "v5.3.4",
+ "version": "v5.3.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/mime.git",
- "reference": "633e4e8afe9e529e5599d71238849a4218dd497b"
+ "reference": "ae887cb3b044658676129f5e97aeb7e9eb69c2d8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/mime/zipball/633e4e8afe9e529e5599d71238849a4218dd497b",
- "reference": "633e4e8afe9e529e5599d71238849a4218dd497b",
+ "url": "https://api.github.com/repos/symfony/mime/zipball/ae887cb3b044658676129f5e97aeb7e9eb69c2d8",
+ "reference": "ae887cb3b044658676129f5e97aeb7e9eb69c2d8",
"shasum": ""
},
"require": {
"mime-type"
],
"support": {
- "source": "https://github.com/symfony/mime/tree/v5.3.4"
+ "source": "https://github.com/symfony/mime/tree/v5.3.7"
},
"funding": [
{
"type": "tidelift"
}
],
- "time": "2021-07-21T12:40:44+00:00"
+ "time": "2021-08-20T11:40:01+00:00"
},
{
"name": "symfony/polyfill-ctype",
},
{
"name": "symfony/process",
- "version": "v4.4.27",
+ "version": "v4.4.30",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
- "reference": "0b7dc5599ac4aa6d7b936c8f7d10abae64f6cf7f"
+ "reference": "13d3161ef63a8ec21eeccaaf9a4d7f784a87a97d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/0b7dc5599ac4aa6d7b936c8f7d10abae64f6cf7f",
- "reference": "0b7dc5599ac4aa6d7b936c8f7d10abae64f6cf7f",
+ "url": "https://api.github.com/repos/symfony/process/zipball/13d3161ef63a8ec21eeccaaf9a4d7f784a87a97d",
+ "reference": "13d3161ef63a8ec21eeccaaf9a4d7f784a87a97d",
"shasum": ""
},
"require": {
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/process/tree/v4.4.27"
+ "source": "https://github.com/symfony/process/tree/v4.4.30"
},
"funding": [
{
"type": "tidelift"
}
],
- "time": "2021-07-23T15:41:52+00:00"
+ "time": "2021-08-04T20:31:23+00:00"
},
{
"name": "symfony/routing",
- "version": "v4.4.27",
+ "version": "v4.4.30",
"source": {
"type": "git",
"url": "https://github.com/symfony/routing.git",
- "reference": "244609821beece97167fa7ba4eef49d2a31862db"
+ "reference": "9ddf033927ad9f30ba2bfd167a7b342cafa13e8e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/routing/zipball/244609821beece97167fa7ba4eef49d2a31862db",
- "reference": "244609821beece97167fa7ba4eef49d2a31862db",
+ "url": "https://api.github.com/repos/symfony/routing/zipball/9ddf033927ad9f30ba2bfd167a7b342cafa13e8e",
+ "reference": "9ddf033927ad9f30ba2bfd167a7b342cafa13e8e",
"shasum": ""
},
"require": {
"url"
],
"support": {
- "source": "https://github.com/symfony/routing/tree/v4.4.27"
+ "source": "https://github.com/symfony/routing/tree/v4.4.30"
},
"funding": [
{
"type": "tidelift"
}
],
- "time": "2021-07-23T15:41:52+00:00"
+ "time": "2021-08-04T21:41:01+00:00"
},
{
"name": "symfony/service-contracts",
},
{
"name": "symfony/translation",
- "version": "v4.4.27",
+ "version": "v4.4.30",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
- "reference": "2e3c0f2bf704d635ba862e7198d72331a62d82ba"
+ "reference": "db0ba1e85280d8ff11e38d53c70f8814d4d740f5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/translation/zipball/2e3c0f2bf704d635ba862e7198d72331a62d82ba",
- "reference": "2e3c0f2bf704d635ba862e7198d72331a62d82ba",
+ "url": "https://api.github.com/repos/symfony/translation/zipball/db0ba1e85280d8ff11e38d53c70f8814d4d740f5",
+ "reference": "db0ba1e85280d8ff11e38d53c70f8814d4d740f5",
"shasum": ""
},
"require": {
"description": "Provides tools to internationalize your application",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/translation/tree/v4.4.27"
+ "source": "https://github.com/symfony/translation/tree/v4.4.30"
},
"funding": [
{
"type": "tidelift"
}
],
- "time": "2021-07-21T13:12:00+00:00"
+ "time": "2021-08-26T05:57:13+00:00"
},
{
"name": "symfony/translation-contracts",
},
{
"name": "symfony/var-dumper",
- "version": "v4.4.27",
+ "version": "v4.4.30",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
- "reference": "391d6d0e7a06ab54eb7c38fab29b8d174471b3ba"
+ "reference": "7f65c44c2ce80d3a0fcdb6385ee0ad535e45660c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-dumper/zipball/391d6d0e7a06ab54eb7c38fab29b8d174471b3ba",
- "reference": "391d6d0e7a06ab54eb7c38fab29b8d174471b3ba",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/7f65c44c2ce80d3a0fcdb6385ee0ad535e45660c",
+ "reference": "7f65c44c2ce80d3a0fcdb6385ee0ad535e45660c",
"shasum": ""
},
"require": {
"dump"
],
"support": {
- "source": "https://github.com/symfony/var-dumper/tree/v4.4.27"
+ "source": "https://github.com/symfony/var-dumper/tree/v4.4.30"
},
"funding": [
{
"type": "tidelift"
}
],
- "time": "2021-07-23T15:41:52+00:00"
+ "time": "2021-08-04T20:31:23+00:00"
},
{
"name": "tijsverkoyen/css-to-inline-styles",
},
{
"name": "composer/composer",
- "version": "2.1.5",
+ "version": "2.1.8",
"source": {
"type": "git",
"url": "https://github.com/composer/composer.git",
- "reference": "ac679902e9f66b85a8f9d8c1c88180f609a8745d"
+ "reference": "24d38e9686092de05214cafa187dc282a5d89497"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/composer/composer/zipball/ac679902e9f66b85a8f9d8c1c88180f609a8745d",
- "reference": "ac679902e9f66b85a8f9d8c1c88180f609a8745d",
+ "url": "https://api.github.com/repos/composer/composer/zipball/24d38e9686092de05214cafa187dc282a5d89497",
+ "reference": "24d38e9686092de05214cafa187dc282a5d89497",
"shasum": ""
},
"require": {
"package"
],
"support": {
- "irc": "irc://irc.freenode.org/composer",
+ "irc": "ircs://irc.libera.chat:6697/composer",
"issues": "https://github.com/composer/composer/issues",
- "source": "https://github.com/composer/composer/tree/2.1.5"
+ "source": "https://github.com/composer/composer/tree/2.1.8"
},
"funding": [
{
"type": "tidelift"
}
],
- "time": "2021-07-23T08:35:47+00:00"
+ "time": "2021-09-15T11:55:15+00:00"
},
{
"name": "composer/metadata-minifier",
},
{
"name": "fakerphp/faker",
- "version": "v1.15.0",
+ "version": "v1.16.0",
"source": {
"type": "git",
"url": "https://github.com/FakerPHP/Faker.git",
- "reference": "89c6201c74db25fa759ff16e78a4d8f32547770e"
+ "reference": "271d384d216e5e5c468a6b28feedf95d49f83b35"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/89c6201c74db25fa759ff16e78a4d8f32547770e",
- "reference": "89c6201c74db25fa759ff16e78a4d8f32547770e",
+ "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/271d384d216e5e5c468a6b28feedf95d49f83b35",
+ "reference": "271d384d216e5e5c468a6b28feedf95d49f83b35",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0",
- "psr/container": "^1.0",
+ "psr/container": "^1.0 || ^2.0",
"symfony/deprecation-contracts": "^2.2"
},
"conflict": {
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "v1.15-dev"
+ "dev-main": "v1.16-dev"
}
},
"autoload": {
],
"support": {
"issues": "https://github.com/FakerPHP/Faker/issues",
- "source": "https://github.com/FakerPHP/Faker/tree/v1.15.0"
+ "source": "https://github.com/FakerPHP/Faker/tree/v1.16.0"
},
- "time": "2021-07-06T20:39:40+00:00"
+ "time": "2021-09-06T14:53:37+00:00"
},
{
"name": "hamcrest/hamcrest-php",
},
"time": "2021-07-22T09:24:00+00:00"
},
- {
- "name": "laravel/browser-kit-testing",
- "version": "v5.2.0",
- "source": {
- "type": "git",
- "url": "https://github.com/laravel/browser-kit-testing.git",
- "reference": "fa0efb279c009e2a276f934f8aff946caf66edc7"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/laravel/browser-kit-testing/zipball/fa0efb279c009e2a276f934f8aff946caf66edc7",
- "reference": "fa0efb279c009e2a276f934f8aff946caf66edc7",
- "shasum": ""
- },
- "require": {
- "ext-dom": "*",
- "ext-json": "*",
- "illuminate/contracts": "~5.7.0|~5.8.0|^6.0",
- "illuminate/database": "~5.7.0|~5.8.0|^6.0",
- "illuminate/http": "~5.7.0|~5.8.0|^6.0",
- "illuminate/support": "~5.7.0|~5.8.0|^6.0",
- "mockery/mockery": "^1.0",
- "php": "^7.1.3|^8.0",
- "phpunit/phpunit": "^7.5|^8.0|^9.3",
- "symfony/console": "^4.2",
- "symfony/css-selector": "^4.2",
- "symfony/dom-crawler": "^4.2",
- "symfony/http-foundation": "^4.2",
- "symfony/http-kernel": "^4.2"
- },
- "require-dev": {
- "laravel/framework": "~5.7.0|~5.8.0|^6.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "5.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Laravel\\BrowserKitTesting\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Taylor Otwell",
- }
- ],
- "description": "Provides backwards compatibility for BrowserKit testing in the latest Laravel release.",
- "keywords": [
- "laravel",
- "testing"
- ],
- "support": {
- "issues": "https://github.com/laravel/browser-kit-testing/issues",
- "source": "https://github.com/laravel/browser-kit-testing/tree/v5.2.0"
- },
- "time": "2020-10-30T08:49:09+00:00"
- },
{
"name": "maximebf/debugbar",
"version": "v1.17.1",
},
{
"name": "mockery/mockery",
- "version": "1.4.3",
+ "version": "1.4.4",
"source": {
"type": "git",
"url": "https://github.com/mockery/mockery.git",
- "reference": "d1339f64479af1bee0e82a0413813fe5345a54ea"
+ "reference": "e01123a0e847d52d186c5eb4b9bf58b0c6d00346"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/mockery/mockery/zipball/d1339f64479af1bee0e82a0413813fe5345a54ea",
- "reference": "d1339f64479af1bee0e82a0413813fe5345a54ea",
+ "url": "https://api.github.com/repos/mockery/mockery/zipball/e01123a0e847d52d186c5eb4b9bf58b0c6d00346",
+ "reference": "e01123a0e847d52d186c5eb4b9bf58b0c6d00346",
"shasum": ""
},
"require": {
],
"support": {
"issues": "https://github.com/mockery/mockery/issues",
- "source": "https://github.com/mockery/mockery/tree/1.4.3"
+ "source": "https://github.com/mockery/mockery/tree/1.4.4"
},
- "time": "2021-02-24T09:51:49+00:00"
+ "time": "2021-09-13T15:28:59+00:00"
},
{
"name": "myclabs/deep-copy",
},
{
"name": "phpdocumentor/type-resolver",
- "version": "1.4.0",
+ "version": "1.5.0",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/TypeResolver.git",
- "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0"
+ "reference": "30f38bffc6f24293dadd1823936372dfa9e86e2f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0",
- "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0",
+ "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/30f38bffc6f24293dadd1823936372dfa9e86e2f",
+ "reference": "30f38bffc6f24293dadd1823936372dfa9e86e2f",
"shasum": ""
},
"require": {
"phpdocumentor/reflection-common": "^2.0"
},
"require-dev": {
- "ext-tokenizer": "*"
+ "ext-tokenizer": "*",
+ "psalm/phar": "^4.8"
},
"type": "library",
"extra": {
"description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
"support": {
"issues": "https://github.com/phpDocumentor/TypeResolver/issues",
- "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.4.0"
+ "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.5.0"
},
- "time": "2020-09-17T18:55:26+00:00"
+ "time": "2021-09-17T15:28:14+00:00"
},
{
"name": "phpspec/prophecy",
- "version": "1.13.0",
+ "version": "1.14.0",
"source": {
"type": "git",
"url": "https://github.com/phpspec/prophecy.git",
- "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea"
+ "reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpspec/prophecy/zipball/be1996ed8adc35c3fd795488a653f4b518be70ea",
- "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea",
+ "url": "https://api.github.com/repos/phpspec/prophecy/zipball/d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e",
+ "reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e",
"shasum": ""
},
"require": {
"doctrine/instantiator": "^1.2",
- "php": "^7.2 || ~8.0, <8.1",
+ "php": "^7.2 || ~8.0, <8.2",
"phpdocumentor/reflection-docblock": "^5.2",
"sebastian/comparator": "^3.0 || ^4.0",
"sebastian/recursion-context": "^3.0 || ^4.0"
},
"require-dev": {
- "phpspec/phpspec": "^6.0",
+ "phpspec/phpspec": "^6.0 || ^7.0",
"phpunit/phpunit": "^8.0 || ^9.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.11.x-dev"
+ "dev-master": "1.x-dev"
}
},
"autoload": {
],
"support": {
"issues": "https://github.com/phpspec/prophecy/issues",
- "source": "https://github.com/phpspec/prophecy/tree/1.13.0"
+ "source": "https://github.com/phpspec/prophecy/tree/1.14.0"
},
- "time": "2021-03-17T13:42:18+00:00"
+ "time": "2021-09-10T09:02:12+00:00"
},
{
"name": "phpunit/php-code-coverage",
- "version": "9.2.6",
+ "version": "9.2.7",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
- "reference": "f6293e1b30a2354e8428e004689671b83871edde"
+ "reference": "d4c798ed8d51506800b441f7a13ecb0f76f12218"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f6293e1b30a2354e8428e004689671b83871edde",
- "reference": "f6293e1b30a2354e8428e004689671b83871edde",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/d4c798ed8d51506800b441f7a13ecb0f76f12218",
+ "reference": "d4c798ed8d51506800b441f7a13ecb0f76f12218",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"ext-xmlwriter": "*",
- "nikic/php-parser": "^4.10.2",
+ "nikic/php-parser": "^4.12.0",
"php": ">=7.3",
"phpunit/php-file-iterator": "^3.0.3",
"phpunit/php-text-template": "^2.0.2",
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
- "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.6"
+ "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.7"
},
"funding": [
{
"type": "github"
}
],
- "time": "2021-03-28T07:26:59+00:00"
+ "time": "2021-09-17T05:39:03+00:00"
},
{
"name": "phpunit/php-file-iterator",
},
{
"name": "phpunit/phpunit",
- "version": "9.5.8",
+ "version": "9.5.9",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "191768ccd5c85513b4068bdbe99bb6390c7d54fb"
+ "reference": "ea8c2dfb1065eb35a79b3681eee6e6fb0a6f273b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/191768ccd5c85513b4068bdbe99bb6390c7d54fb",
- "reference": "191768ccd5c85513b4068bdbe99bb6390c7d54fb",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ea8c2dfb1065eb35a79b3681eee6e6fb0a6f273b",
+ "reference": "ea8c2dfb1065eb35a79b3681eee6e6fb0a6f273b",
"shasum": ""
},
"require": {
],
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
- "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.8"
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.9"
},
"funding": [
{
"type": "github"
}
],
- "time": "2021-07-31T15:17:34+00:00"
+ "time": "2021-08-31T06:47:40+00:00"
},
{
"name": "react/promise",
},
{
"name": "seld/phar-utils",
- "version": "1.1.1",
+ "version": "1.1.2",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/phar-utils.git",
- "reference": "8674b1d84ffb47cc59a101f5d5a3b61e87d23796"
+ "reference": "749042a2315705d2dfbbc59234dd9ceb22bf3ff0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/8674b1d84ffb47cc59a101f5d5a3b61e87d23796",
- "reference": "8674b1d84ffb47cc59a101f5d5a3b61e87d23796",
+ "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/749042a2315705d2dfbbc59234dd9ceb22bf3ff0",
+ "reference": "749042a2315705d2dfbbc59234dd9ceb22bf3ff0",
"shasum": ""
},
"require": {
],
"support": {
"issues": "https://github.com/Seldaek/phar-utils/issues",
- "source": "https://github.com/Seldaek/phar-utils/tree/master"
+ "source": "https://github.com/Seldaek/phar-utils/tree/1.1.2"
},
- "time": "2020-07-07T18:42:57+00:00"
+ "time": "2021-08-19T21:01:38+00:00"
},
{
"name": "symfony/dom-crawler",
- "version": "v4.4.27",
+ "version": "v5.3.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/dom-crawler.git",
- "reference": "86aa075c9e0b13ac7db8d73d1f9d8b656143881a"
+ "reference": "c7eef3a60ccfdd8eafe07f81652e769ac9c7146c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/86aa075c9e0b13ac7db8d73d1f9d8b656143881a",
- "reference": "86aa075c9e0b13ac7db8d73d1f9d8b656143881a",
+ "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/c7eef3a60ccfdd8eafe07f81652e769ac9c7146c",
+ "reference": "c7eef3a60ccfdd8eafe07f81652e769ac9c7146c",
"shasum": ""
},
"require": {
- "php": ">=7.1.3",
+ "php": ">=7.2.5",
+ "symfony/deprecation-contracts": "^2.1",
"symfony/polyfill-ctype": "~1.8",
"symfony/polyfill-mbstring": "~1.0",
"symfony/polyfill-php80": "^1.16"
},
"require-dev": {
"masterminds/html5": "^2.6",
- "symfony/css-selector": "^3.4|^4.0|^5.0"
+ "symfony/css-selector": "^4.4|^5.0"
},
"suggest": {
"symfony/css-selector": ""
"description": "Eases DOM navigation for HTML and XML documents",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/dom-crawler/tree/v4.4.27"
+ "source": "https://github.com/symfony/dom-crawler/tree/v5.3.7"
},
"funding": [
{
"type": "tidelift"
}
],
- "time": "2021-07-23T15:41:52+00:00"
+ "time": "2021-08-29T19:32:13+00:00"
},
{
"name": "symfony/filesystem",
"platform-overrides": {
"php": "7.3.0"
},
- "plugin-api-version": "2.0.0"
+ "plugin-api-version": "2.1.0"
}
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Str;
class CreateJointPermissionsTable extends Migration
{
// Ensure unique name
while (DB::table('roles')->where('name', '=', $publicRoleData['display_name'])->count() > 0) {
- $publicRoleData['display_name'] = $publicRoleData['display_name'] . str_random(2);
+ $publicRoleData['display_name'] = $publicRoleData['display_name'] . Str::random(2);
}
$publicRoleId = DB::table('roles')->insertGetId($publicRoleData);
--- /dev/null
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class CreateMfaValuesTable extends Migration
+{
+ /**
+ * Run the migrations.
+ *
+ * @return void
+ */
+ public function up()
+ {
+ Schema::create('mfa_values', function (Blueprint $table) {
+ $table->increments('id');
+ $table->integer('user_id')->index();
+ $table->string('method', 20)->index();
+ $table->text('value');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('mfa_values');
+ }
+}
--- /dev/null
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class AddMfaEnforcedToRolesTable extends Migration
+{
+ /**
+ * Run the migrations.
+ *
+ * @return void
+ */
+ public function up()
+ {
+ Schema::table('roles', function (Blueprint $table) {
+ $table->boolean('mfa_enforced');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('roles', function (Blueprint $table) {
+ $table->dropColumn('mfa_enforced');
+ });
+ }
+}
--- /dev/null
+<?php
+
+use Carbon\Carbon;
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Support\Facades\DB;
+
+class AddExportRolePermission extends Migration
+{
+ /**
+ * Run the migrations.
+ *
+ * @return void
+ */
+ public function up()
+ {
+ // Create new templates-manage permission and assign to admin role
+ $roles = DB::table('roles')->get('id');
+ $permissionId = DB::table('role_permissions')->insertGetId([
+ 'name' => 'content-export',
+ 'display_name' => 'Export Content',
+ 'created_at' => Carbon::now()->toDateTimeString(),
+ 'updated_at' => Carbon::now()->toDateTimeString(),
+ ]);
+
+ $permissionRoles = $roles->map(function ($role) use ($permissionId) {
+ return [
+ 'role_id' => $role->id,
+ 'permission_id' => $permissionId,
+ ];
+ })->values()->toArray();
+
+ DB::table('permission_role')->insert($permissionRoles);
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ // Remove content-export permission
+ $contentExportPermission = DB::table('role_permissions')
+ ->where('name', '=', 'content-export')->first();
+
+ DB::table('permission_role')->where('permission_id', '=', $contentExportPermission->id)->delete();
+ DB::table('role_permissions')->where('id', '=', 'content-export')->delete();
+ }
+}
--- /dev/null
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class AddActivitiesIpColumn extends Migration
+{
+ /**
+ * Run the migrations.
+ *
+ * @return void
+ */
+ public function up()
+ {
+ Schema::table('activities', function (Blueprint $table) {
+ $table->string('ip', 45)->after('user_id');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('activities', function (Blueprint $table) {
+ $table->dropColumn('ip');
+ });
+ }
+}
"": {
"dependencies": {
"clipboard": "^2.0.8",
- "codemirror": "^5.61.1",
+ "codemirror": "^5.62.3",
"dropzone": "^5.9.2",
- "markdown-it": "^12.0.6",
+ "markdown-it": "^12.2.0",
"markdown-it-task-lists": "^2.1.1",
- "sortablejs": "^1.13.0"
+ "sortablejs": "^1.14.0"
},
"devDependencies": {
- "chokidar-cli": "^2.1.0",
- "esbuild": "0.12.8",
+ "chokidar-cli": "^3.0.0",
+ "esbuild": "0.12.22",
"livereload": "^0.9.3",
"npm-run-all": "^4.1.5",
"punycode": "^2.1.1",
- "sass": "^1.34.1"
+ "sass": "^1.38.0"
}
},
"node_modules/ansi-regex": {
}
},
"node_modules/anymatch": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz",
- "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==",
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
+ "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
"dev": true,
"dependencies": {
"normalize-path": "^3.0.0",
}
},
"node_modules/chokidar": {
- "version": "3.4.2",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz",
- "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==",
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz",
+ "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==",
"dev": true,
"dependencies": {
- "anymatch": "~3.1.1",
+ "anymatch": "~3.1.2",
"braces": "~3.0.2",
- "glob-parent": "~5.1.0",
+ "glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
- "readdirp": "~3.4.0"
+ "readdirp": "~3.6.0"
},
"engines": {
"node": ">= 8.10.0"
},
"optionalDependencies": {
- "fsevents": "~2.1.2"
+ "fsevents": "~2.3.2"
}
},
"node_modules/chokidar-cli": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/chokidar-cli/-/chokidar-cli-2.1.0.tgz",
- "integrity": "sha512-6n21AVpW6ywuEPoxJcLXMA2p4T+SLjWsXKny/9yTWFz0kKxESI3eUylpeV97LylING/27T/RVTY0f2/0QaWq9Q==",
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/chokidar-cli/-/chokidar-cli-3.0.0.tgz",
+ "integrity": "sha512-xVW+Qeh7z15uZRxHOkP93Ux8A0xbPzwK4GaqD8dQOYc34TlkqUhVSS59fK36DOp5WdJlrRzlYSy02Ht99FjZqQ==",
"dev": true,
"dependencies": {
- "chokidar": "^3.2.3",
+ "chokidar": "^3.5.2",
"lodash.debounce": "^4.0.8",
"lodash.throttle": "^4.1.1",
"yargs": "^13.3.0"
}
},
"node_modules/codemirror": {
- "version": "5.61.1",
- "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.61.1.tgz",
- "integrity": "sha512-+D1NZjAucuzE93vJGbAaXzvoBHwp9nJZWWWF9utjv25+5AZUiah6CIlfb4ikG4MoDsFsCG8niiJH5++OO2LgIQ=="
+ "version": "5.62.3",
+ "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.62.3.tgz",
+ "integrity": "sha512-zZAyOfN8TU67ngqrxhOgtkSAGV9jSpN1snbl8elPtnh9Z5A11daR405+dhLzLnuXrwX0WCShWlybxPN3QC/9Pg=="
},
"node_modules/color-convert": {
"version": "1.9.3",
}
},
"node_modules/esbuild": {
- "version": "0.12.8",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.8.tgz",
- "integrity": "sha512-sx/LwlP/SWTGsd9G4RlOPrXnIihAJ2xwBUmzoqe2nWwbXORMQWtAGNJNYLBJJqa3e9PWvVzxdrtyFZJcr7D87g==",
+ "version": "0.12.22",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.22.tgz",
+ "integrity": "sha512-yWCr9RoFehpqoe/+MwZXJpYOEIt7KOEvNnjIeMZpMSyQt+KCBASM3y7yViiN5dJRphf1wGdUz1+M4rTtWd/ulA==",
"dev": true,
"hasInstallScript": true,
"bin": {
}
},
"node_modules/fsevents": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz",
- "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==",
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
+ "hasInstallScript": true,
"optional": true,
"os": [
"darwin"
"integrity": "sha512-w677WnINxFkuixAoUEXOStewzLYGI76XVag+0JWMMEyjJQKs0ibWZMxkTlB96Lm3EjZ7IeOxVziBEbtxVQqQZA==",
"dev": true
},
- "node_modules/livereload/node_modules/chokidar": {
- "version": "3.5.1",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz",
- "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==",
- "dev": true,
- "dependencies": {
- "anymatch": "~3.1.1",
- "braces": "~3.0.2",
- "glob-parent": "~5.1.0",
- "is-binary-path": "~2.1.0",
- "is-glob": "~4.0.1",
- "normalize-path": "~3.0.0",
- "readdirp": "~3.5.0"
- },
- "engines": {
- "node": ">= 8.10.0"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.1"
- }
- },
- "node_modules/livereload/node_modules/fsevents": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
- "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
- "dev": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
- "node_modules/livereload/node_modules/readdirp": {
- "version": "3.5.0",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
- "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
- "dev": true,
- "dependencies": {
- "picomatch": "^2.2.1"
- },
- "engines": {
- "node": ">=8.10.0"
- }
- },
"node_modules/load-json-file": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
"dev": true
},
"node_modules/markdown-it": {
- "version": "12.0.6",
- "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.0.6.tgz",
- "integrity": "sha512-qv3sVLl4lMT96LLtR7xeRJX11OUFjsaD5oVat2/SNBIb21bJXwal2+SklcRbTwGwqWpWH/HRtYavOoJE+seL8w==",
+ "version": "12.2.0",
+ "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.2.0.tgz",
+ "integrity": "sha512-Wjws+uCrVQRqOoJvze4HCqkKl1AsSh95iFAeQDwnyfxM09divCBSXlDR1uTvyUP3Grzpn4Ru8GeCxYPM8vkCQg==",
"dependencies": {
"argparse": "^2.0.1",
"entities": "~2.1.0",
}
},
"node_modules/path-parse": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
- "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true
},
"node_modules/path-type": {
}
},
"node_modules/readdirp": {
- "version": "3.4.0",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz",
- "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==",
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"dependencies": {
"picomatch": "^2.2.1"
}
},
"node_modules/sass": {
- "version": "1.34.1",
- "resolved": "https://registry.npmjs.org/sass/-/sass-1.34.1.tgz",
- "integrity": "sha512-scLA7EIZM+MmYlej6sdVr0HRbZX5caX5ofDT9asWnUJj21oqgsC+1LuNfm0eg+vM0fCTZHhwImTiCU0sx9h9CQ==",
+ "version": "1.38.0",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.38.0.tgz",
+ "integrity": "sha512-WBccZeMigAGKoI+NgD7Adh0ab1HUq+6BmyBUEaGxtErbUtWUevEbdgo5EZiJQofLUGcKtlNaO2IdN73AHEua5g==",
"dev": true,
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0"
"dev": true
},
"node_modules/sortablejs": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.13.0.tgz",
- "integrity": "sha512-RBJirPY0spWCrU5yCmWM1eFs/XgX2J5c6b275/YyxFRgnzPhKl/TDeU2hNR8Dt7ITq66NRPM4UlOt+e5O4CFHg=="
+ "version": "1.14.0",
+ "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz",
+ "integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w=="
},
"node_modules/spdx-correct": {
"version": "3.1.1",
}
},
"anymatch": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz",
- "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==",
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
+ "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
"dev": true,
"requires": {
"normalize-path": "^3.0.0",
}
},
"chokidar": {
- "version": "3.4.2",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz",
- "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==",
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz",
+ "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==",
"dev": true,
"requires": {
- "anymatch": "~3.1.1",
+ "anymatch": "~3.1.2",
"braces": "~3.0.2",
- "fsevents": "~2.1.2",
- "glob-parent": "~5.1.0",
+ "fsevents": "~2.3.2",
+ "glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
- "readdirp": "~3.4.0"
+ "readdirp": "~3.6.0"
}
},
"chokidar-cli": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/chokidar-cli/-/chokidar-cli-2.1.0.tgz",
- "integrity": "sha512-6n21AVpW6ywuEPoxJcLXMA2p4T+SLjWsXKny/9yTWFz0kKxESI3eUylpeV97LylING/27T/RVTY0f2/0QaWq9Q==",
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/chokidar-cli/-/chokidar-cli-3.0.0.tgz",
+ "integrity": "sha512-xVW+Qeh7z15uZRxHOkP93Ux8A0xbPzwK4GaqD8dQOYc34TlkqUhVSS59fK36DOp5WdJlrRzlYSy02Ht99FjZqQ==",
"dev": true,
"requires": {
- "chokidar": "^3.2.3",
+ "chokidar": "^3.5.2",
"lodash.debounce": "^4.0.8",
"lodash.throttle": "^4.1.1",
"yargs": "^13.3.0"
}
},
"codemirror": {
- "version": "5.61.1",
- "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.61.1.tgz",
- "integrity": "sha512-+D1NZjAucuzE93vJGbAaXzvoBHwp9nJZWWWF9utjv25+5AZUiah6CIlfb4ikG4MoDsFsCG8niiJH5++OO2LgIQ=="
+ "version": "5.62.3",
+ "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.62.3.tgz",
+ "integrity": "sha512-zZAyOfN8TU67ngqrxhOgtkSAGV9jSpN1snbl8elPtnh9Z5A11daR405+dhLzLnuXrwX0WCShWlybxPN3QC/9Pg=="
},
"color-convert": {
"version": "1.9.3",
}
},
"esbuild": {
- "version": "0.12.8",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.8.tgz",
- "integrity": "sha512-sx/LwlP/SWTGsd9G4RlOPrXnIihAJ2xwBUmzoqe2nWwbXORMQWtAGNJNYLBJJqa3e9PWvVzxdrtyFZJcr7D87g==",
+ "version": "0.12.22",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.22.tgz",
+ "integrity": "sha512-yWCr9RoFehpqoe/+MwZXJpYOEIt7KOEvNnjIeMZpMSyQt+KCBASM3y7yViiN5dJRphf1wGdUz1+M4rTtWd/ulA==",
"dev": true
},
"escape-string-regexp": {
}
},
"fsevents": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz",
- "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==",
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"optional": true
},
"livereload-js": "^3.3.1",
"opts": ">= 1.2.0",
"ws": "^7.4.3"
- },
- "dependencies": {
- "chokidar": {
- "version": "3.5.1",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz",
- "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==",
- "dev": true,
- "requires": {
- "anymatch": "~3.1.1",
- "braces": "~3.0.2",
- "fsevents": "~2.3.1",
- "glob-parent": "~5.1.0",
- "is-binary-path": "~2.1.0",
- "is-glob": "~4.0.1",
- "normalize-path": "~3.0.0",
- "readdirp": "~3.5.0"
- }
- },
- "fsevents": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
- "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
- "dev": true,
- "optional": true
- },
- "readdirp": {
- "version": "3.5.0",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
- "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
- "dev": true,
- "requires": {
- "picomatch": "^2.2.1"
- }
- }
}
},
"livereload-js": {
"dev": true
},
"markdown-it": {
- "version": "12.0.6",
- "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.0.6.tgz",
- "integrity": "sha512-qv3sVLl4lMT96LLtR7xeRJX11OUFjsaD5oVat2/SNBIb21bJXwal2+SklcRbTwGwqWpWH/HRtYavOoJE+seL8w==",
+ "version": "12.2.0",
+ "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.2.0.tgz",
+ "integrity": "sha512-Wjws+uCrVQRqOoJvze4HCqkKl1AsSh95iFAeQDwnyfxM09divCBSXlDR1uTvyUP3Grzpn4Ru8GeCxYPM8vkCQg==",
"requires": {
"argparse": "^2.0.1",
"entities": "~2.1.0",
"dev": true
},
"path-parse": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
- "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true
},
"path-type": {
}
},
"readdirp": {
- "version": "3.4.0",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz",
- "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==",
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"requires": {
"picomatch": "^2.2.1"
}
},
"sass": {
- "version": "1.34.1",
- "resolved": "https://registry.npmjs.org/sass/-/sass-1.34.1.tgz",
- "integrity": "sha512-scLA7EIZM+MmYlej6sdVr0HRbZX5caX5ofDT9asWnUJj21oqgsC+1LuNfm0eg+vM0fCTZHhwImTiCU0sx9h9CQ==",
+ "version": "1.38.0",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.38.0.tgz",
+ "integrity": "sha512-WBccZeMigAGKoI+NgD7Adh0ab1HUq+6BmyBUEaGxtErbUtWUevEbdgo5EZiJQofLUGcKtlNaO2IdN73AHEua5g==",
"dev": true,
"requires": {
"chokidar": ">=3.0.0 <4.0.0"
"dev": true
},
"sortablejs": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.13.0.tgz",
- "integrity": "sha512-RBJirPY0spWCrU5yCmWM1eFs/XgX2J5c6b275/YyxFRgnzPhKl/TDeU2hNR8Dt7ITq66NRPM4UlOt+e5O4CFHg=="
+ "version": "1.14.0",
+ "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz",
+ "integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w=="
},
"spdx-correct": {
"version": "3.1.1",
"permissions": "chown -R $USER:$USER bootstrap/cache storage public/uploads"
},
"devDependencies": {
- "chokidar-cli": "^2.1.0",
- "esbuild": "0.12.8",
+ "chokidar-cli": "^3.0.0",
+ "esbuild": "0.12.22",
"livereload": "^0.9.3",
"npm-run-all": "^4.1.5",
"punycode": "^2.1.1",
- "sass": "^1.34.1"
+ "sass": "^1.38.0"
},
"dependencies": {
"clipboard": "^2.0.8",
- "codemirror": "^5.61.1",
+ "codemirror": "^5.62.3",
"dropzone": "^5.9.2",
- "markdown-it": "^12.0.6",
+ "markdown-it": "^12.2.0",
"markdown-it-task-lists": "^2.1.1",
- "sortablejs": "^1.13.0"
+ "sortablejs": "^1.14.0"
}
}
<server name="LOG_CHANNEL" value="single"/>
<server name="AUTH_METHOD" value="standard"/>
<server name="DISABLE_EXTERNAL_SERVICES" value="true"/>
+ <server name="ALLOW_UNTRUSTED_SERVER_FETCHING" value="false"/>
<server name="AVATAR_URL" value=""/>
<server name="LDAP_START_TLS" value="false"/>
<server name="LDAP_VERSION" value="3"/>
[](https://github.com/BookStackApp/BookStack/releases/latest)
[](https://github.com/BookStackApp/BookStack/blob/master/LICENSE)
[](https://crowdin.com/project/bookstack)
-[](https://github.com/BookStackApp/BookStack/actions)
[](https://discord.gg/ztkBqR2)
[](https://gh-stats.bookstackapp.com/)
+[](https://github.com/BookStackApp/BookStack/actions)
+[](https://github.styleci.io/repos/41589337)
A platform for storing and organising information and documentation. Details for BookStack can be found on the official website at https://www.bookstackapp.com/.
* [OneLogin's SAML PHP Toolkit](https://github.com/onelogin/php-saml)
* [League/CommonMark](https://commonmark.thephpleague.com/)
* [League/Flysystem](https://flysystem.thephpleague.com)
-* [StyleCI](https://styleci.io/)
\ No newline at end of file
+* [StyleCI](https://styleci.io/)
+* [pragmarx/google2fa](https://github.com/antonioribeiro/google2fa)
+* [Bacon/BaconQrCode](https://github.com/Bacon/BaconQrCode)
\ No newline at end of file
'favourite_add_notification' => '":name" has been added to your favourites',
'favourite_remove_notification' => '":name" has been removed from your favourites',
+ // MFA
+ 'mfa_setup_method_notification' => 'Multi-factor method successfully configured',
+ 'mfa_remove_method_notification' => 'Multi-factor method successfully removed',
+
// Other
'commented_on' => 'تم التعليق',
'permissions_update' => 'تحديث الأذونات',
'user_invite_page_welcome' => 'مرحبا بكم في :appName!',
'user_invite_page_text' => 'لإكمال حسابك والحصول على حق الوصول تحتاج إلى تعيين كلمة مرور سيتم استخدامها لتسجيل الدخول إلى :appName في الزيارات المستقبلية.',
'user_invite_page_confirm_button' => 'تأكيد كلمة المرور',
- 'user_invite_success' => 'مجموعة كلمات المرور، لديك الآن حق الوصول إلى :appName!'
+ 'user_invite_success' => 'مجموعة كلمات المرور، لديك الآن حق الوصول إلى :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_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.',
];
\ No newline at end of file
'reset' => 'إعادة تعيين',
'remove' => 'إزالة',
'add' => 'إضافة',
+ 'configure' => 'Configure',
'fullscreen' => 'شاشة كاملة',
'favourite' => 'Favourite',
'unfavourite' => 'Unfavourite',
'no_activity' => 'لا يوجد نشاط لعرضه',
'no_items' => 'لا توجد عناصر متوفرة',
'back_to_top' => 'العودة إلى الأعلى',
+ 'skip_to_main_content' => 'Skip to main content',
'toggle_details' => 'عرض / إخفاء التفاصيل',
'toggle_thumbnails' => 'عرض / إخفاء الصور المصغرة',
'details' => 'التفاصيل',
'export_html' => 'صفحة ويب',
'export_pdf' => 'ملف PDF',
'export_text' => 'ملف نص عادي',
+ 'export_md' => 'Markdown File',
// Permissions and restrictions
'permissions' => 'الأذونات',
'shelves_permissions' => 'أذونات رف الكتب',
'shelves_permissions_updated' => 'تم تحديث أذونات رف الكتب',
'shelves_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' => 'سيؤدي هذا إلى تطبيق إعدادات الأذونات الحالية لهذا الرف على جميع الكتب المتضمنة فيه. قبل التفعيل، تأكد من حفظ أي تغييرات في أذونات هذا الرف.',
'recycle_bin' => 'سلة المحذوفات',
'recycle_bin_desc' => 'هنا يمكنك استعادة العناصر التي تم حذفها أو اختيار إزالتها نهائيا من النظام. هذه القائمة غير مصفاة خلافاً لقوائم الأنشطة المماثلة في النظام حيث يتم تطبيق عوامل تصفية الأذونات.',
'recycle_bin_deleted_item' => 'عنصر محذوف',
+ 'recycle_bin_deleted_parent' => 'Parent',
'recycle_bin_deleted_by' => 'حُذف بواسطة',
'recycle_bin_deleted_at' => 'وقت الحذف',
'recycle_bin_permanently_delete' => 'حُذف نهائيًا',
'recycle_bin_restore_list' => 'العناصر المراد استرجاعها',
'recycle_bin_restore_confirm' => 'سيعيد هذا الإجراء العنصر المحذوف ، بما في ذلك أي عناصر فرعية ، إلى موقعه الأصلي. إذا تم حذف الموقع الأصلي منذ ذلك الحين ، وهو الآن في سلة المحذوفات ، فسيلزم أيضًا استعادة العنصر الأصلي.',
'recycle_bin_restore_deleted_parent' => 'تم حذف أصل هذا العنصر أيضًا. سيبقى حذفه حتى يتم استعادة ذلك الأصل أيضًا.',
+ 'recycle_bin_restore_parent' => 'Restore Parent',
'recycle_bin_destroy_notification' => 'المحذوف: قُم بعد إجمالي العناصر من سلة المحذوفات.',
'recycle_bin_restore_notification' => 'المرتجع: قُم بعد إجمالي العناصر من سلة المحذوفات.',
'audit_table_user' => 'المستخدم',
'audit_table_event' => 'الحدث',
'audit_table_related' => 'العنصر أو التفاصيل ذات الصلة',
+ 'audit_table_ip' => 'IP Address',
'audit_table_date' => 'تاريخ النشاط',
'audit_date_from' => 'نطاق التاريخ من',
'audit_date_to' => 'نطاق التاريخ إلى',
'role_details' => 'تفاصيل الدور',
'role_name' => 'اسم الدور',
'role_desc' => 'وصف مختصر للدور',
+ 'role_mfa_enforced' => 'Requires Multi-Factor Authentication',
'role_external_auth_id' => 'ربط الحساب بمواقع التواصل',
'role_system' => 'أذونات النظام',
'role_manage_users' => 'إدارة المستخدمين',
'role_manage_page_templates' => 'إدارة قوالب الصفحة',
'role_access_api' => 'الوصول إلى واجهة برمجة تطبيقات النظام API',
'role_manage_settings' => 'إدارة إعدادات التطبيق',
+ 'role_export_content' => 'Export content',
'role_asset' => 'أذونات الأصول',
'roles_system_warning' => 'اعلم أن الوصول إلى أي من الأذونات الثلاثة المذكورة أعلاه يمكن أن يسمح للمستخدم بتغيير امتيازاته الخاصة أو امتيازات الآخرين في النظام. قم بتعيين الأدوار مع هذه الأذونات فقط للمستخدمين الموثوق بهم.',
'role_asset_desc' => 'تتحكم هذه الأذونات في الوصول الافتراضي إلى الأصول داخل النظام. ستتجاوز الأذونات الخاصة بالكتب والفصول والصفحات هذه الأذونات.',
'users_api_tokens_create' => 'قم بإنشاء رمز مميز',
'users_api_tokens_expires' => 'انتهاء مدة الصلاحية',
'users_api_tokens_docs' => 'وثائق API',
+ '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',
// API Tokens
'user_api_token_create' => 'قم بإنشاء رمز API',
'it' => 'Italian',
'ja' => '日本語',
'ko' => '한국어',
+ 'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nl' => 'Nederlands',
'nb' => 'Norsk (Bokmål)',
'alpha_dash' => 'يجب أن يقتصر :attribute على حروف أو أرقام أو شرطات فقط.',
'alpha_num' => 'يجب أن يقتصر :attribute على الحروف والأرقام فقط.',
'array' => 'يجب أن تكون السمة مصفوفة.',
+ 'backup_codes' => 'The provided code is not valid or has already been used.',
'before' => 'يجب أن يكون التاريخ :attribute قبل :date.',
'between' => [
'numeric' => 'يجب أن يكون :attribute بين :min و :max.',
],
'string' => 'يجب أن تكون السمة: سلسلة.',
'timezone' => 'يجب أن تكون :attribute منطقة صالحة.',
+ 'totp' => 'The provided code is not valid or has expired.',
'unique' => 'تم حجز :attribute من قبل.',
'url' => 'صيغة :attribute غير صالحة.',
'uploaded' => 'تعذر تحميل الملف. قد لا يقبل الخادم ملفات بهذا الحجم.',
'favourite_add_notification' => '":name" has been added to your favourites',
'favourite_remove_notification' => '":name" has been removed from your favourites',
+ // MFA
+ 'mfa_setup_method_notification' => 'Multi-factor method successfully configured',
+ 'mfa_remove_method_notification' => 'Multi-factor method successfully removed',
+
// Other
'commented_on' => 'коментирано на',
'permissions_update' => 'updated permissions',
'user_invite_page_welcome' => 'Добре дошли в :appName!',
'user_invite_page_text' => 'За да финализирате вашият акаунт и да получите достъп трябва да определите парола, която да бъде използвана за следващия влизания в :appName.',
'user_invite_page_confirm_button' => 'Потвърди паролата',
- 'user_invite_success' => 'Паролата е потвърдена и вече имате достъп до :appName!'
+ 'user_invite_success' => 'Паролата е потвърдена и вече имате достъп до :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_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.',
];
\ No newline at end of file
'reset' => 'Нулирай',
'remove' => 'Премахване',
'add' => 'Добави',
+ 'configure' => 'Configure',
'fullscreen' => 'Пълен екран',
'favourite' => 'Favourite',
'unfavourite' => 'Unfavourite',
'no_activity' => 'Няма активност за показване',
'no_items' => 'Няма налични артикули',
'back_to_top' => 'Върнете се в началото',
+ 'skip_to_main_content' => 'Skip to main content',
'toggle_details' => 'Активирай детайли',
'toggle_thumbnails' => 'Активирай миниатюри',
'details' => 'Подробности',
'export_html' => 'Прикачени уеб файлове',
'export_pdf' => 'PDF файл',
'export_text' => 'Обикновен текстов файл',
+ 'export_md' => 'Markdown File',
// Permissions and restrictions
'permissions' => 'Права',
'shelves_permissions' => 'Настройки за достъп до рафта с книги',
'shelves_permissions_updated' => 'Настройките за достъп до рафта с книги е обновен',
'shelves_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' => 'Това ще приложи настоящите настройки за достъп на този рафт с книги за всички книги, съдържащи се в него. Преди да активирате, уверете се, че всички промени в настройките за достъп на този рафт са запазени.',
'recycle_bin' => 'Кошче',
'recycle_bin_desc' => 'Here you can restore items that have been deleted or choose to permanently remove them from the system. This list is unfiltered unlike similar activity lists in the system where permission filters are applied.',
'recycle_bin_deleted_item' => 'Изтрит предмет',
+ 'recycle_bin_deleted_parent' => 'Parent',
'recycle_bin_deleted_by' => 'Изтрит от',
'recycle_bin_deleted_at' => 'Час на изтриване',
'recycle_bin_permanently_delete' => 'Permanently Delete',
'recycle_bin_restore_list' => 'Items to be Restored',
'recycle_bin_restore_confirm' => 'This action will restore the deleted item, including any child elements, to their original location. If the original location has since been deleted, and is now in the recycle bin, the parent item will also need to be restored.',
'recycle_bin_restore_deleted_parent' => 'The parent of this item has also been deleted. These will remain deleted until that parent is also restored.',
+ 'recycle_bin_restore_parent' => 'Restore Parent',
'recycle_bin_destroy_notification' => 'Deleted :count total items from the recycle bin.',
'recycle_bin_restore_notification' => 'Restored :count total items from the recycle bin.',
'audit_table_user' => 'Потребител',
'audit_table_event' => 'Събитие',
'audit_table_related' => 'Related Item or Detail',
+ 'audit_table_ip' => 'IP Address',
'audit_table_date' => 'Дата на активност',
'audit_date_from' => 'Време от',
'audit_date_to' => 'Време до',
'role_details' => 'Детайли на роля',
'role_name' => 'Име на ролята',
'role_desc' => 'Кратко описание на ролята',
+ 'role_mfa_enforced' => 'Requires Multi-Factor Authentication',
'role_external_auth_id' => 'Външни ауторизиращи ID-a',
'role_system' => 'Настойки за достъп на системата',
'role_manage_users' => 'Управление на потребители',
'role_manage_page_templates' => 'Управление на шаблони на страници',
'role_access_api' => 'Достъп до API на системата',
'role_manage_settings' => 'Управление на настройките на приложението',
+ 'role_export_content' => 'Export content',
'role_asset' => 'Настройки за достъп до активи',
'roles_system_warning' => 'Важно: Добавянето на потребител в някое от горните три роли може да му позволи да промени собствените си права или правата на другите в системата. Възлагайте тези роли само на доверени потребители.',
'role_asset_desc' => 'Тези настройки за достъп контролират достъпа по подразбиране до активите в системата. Настройките за достъп до книги, глави и страници ще отменят тези настройки.',
'users_api_tokens_create' => 'Create Token',
'users_api_tokens_expires' => 'Expires',
'users_api_tokens_docs' => 'API Documentation',
+ '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',
// API Tokens
'user_api_token_create' => 'Create API Token',
'it' => 'Italian',
'ja' => '日本語',
'ko' => '한국어',
+ 'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nl' => 'Nederlands',
'nb' => 'Norsk (Bokmål)',
'alpha_dash' => ':attribute може да съдържа само букви, числа, тире и долна черта.',
'alpha_num' => ':attribute може да съдържа само букви и числа.',
'array' => ':attribute трябва да е масив (array).',
+ 'backup_codes' => 'The provided code is not valid or has already been used.',
'before' => ':attribute трябва да е дата след :date.',
'between' => [
'numeric' => ':attribute трябва да е между :min и :max.',
],
'string' => 'The :attribute must be a string.',
'timezone' => 'The :attribute must be a valid zone.',
+ 'totp' => 'The provided code is not valid or has expired.',
'unique' => 'The :attribute has already been taken.',
'url' => 'The :attribute format is invalid.',
'uploaded' => 'The file could not be uploaded. The server may not accept files of this size.',
'bookshelf_delete_notification' => 'Polica za knjige Uspješno Izbrisana',
// Favourites
- 'favourite_add_notification' => '":name" has been added to your favourites',
- 'favourite_remove_notification' => '":name" has been removed from your favourites',
+ 'favourite_add_notification' => '":name" je dodan u tvoje favorite',
+ 'favourite_remove_notification' => '":name" je uklonjen iz tvojih favorita',
+
+ // MFA
+ 'mfa_setup_method_notification' => 'Multi-factor method successfully configured',
+ 'mfa_remove_method_notification' => 'Multi-factor method successfully removed',
// Other
'commented_on' => 'je komentarisao/la na',
'user_invite_page_welcome' => 'Dobrodošli na :appName!',
'user_invite_page_text' => 'Da biste završili vaš račun i dobili pristup morate postaviti lozinku koju ćete koristiti da se prijavite na :appName tokom budućih posjeta.',
'user_invite_page_confirm_button' => 'Potvrdi lozinku',
- 'user_invite_success' => 'Lozinka postavljena, sada imate pristup :sppName!'
+ 'user_invite_success' => 'Lozinka postavljena, sada imate pristup :sppName!',
+
+ // 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_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.',
];
\ No newline at end of file
'reset' => 'Resetuj',
'remove' => 'Ukloni',
'add' => 'Dodaj',
+ 'configure' => 'Configure',
'fullscreen' => 'Prikaz preko čitavog ekrana',
- 'favourite' => 'Favourite',
- 'unfavourite' => 'Unfavourite',
- 'next' => 'Next',
- 'previous' => 'Previous',
+ 'favourite' => 'Favorit',
+ 'unfavourite' => 'Ukloni favorit',
+ 'next' => 'Sljedeće',
+ 'previous' => 'Prethodno',
// Sort Options
'sort_options' => 'Opcije sortiranja',
'sort_ascending' => 'Sortiraj uzlazno',
'sort_descending' => 'Sortiraj silazno',
'sort_name' => 'Ime',
- 'sort_default' => 'Default',
+ 'sort_default' => 'Početne postavke',
'sort_created_at' => 'Datum kreiranja',
'sort_updated_at' => 'Datum ažuriranja',
'no_activity' => 'Nema aktivnosti za prikazivanje',
'no_items' => 'Nema dostupnih stavki',
'back_to_top' => 'Povratak na vrh',
+ 'skip_to_main_content' => 'Idi odmah na glavni sadržaj',
'toggle_details' => 'Vidi detalje',
'toggle_thumbnails' => 'Vidi prikaze slika',
'details' => 'Detalji',
'breadcrumb' => 'Navigacijske stavke',
// Header
- 'header_menu_expand' => 'Expand Header Menu',
+ 'header_menu_expand' => 'Otvori meni u zaglavlju',
'profile_menu' => 'Meni profila',
'view_profile' => 'Pogledaj profil',
'edit_profile' => 'Izmjeni profil',
// Layout tabs
'tab_info' => 'Informacije',
- 'tab_info_label' => 'Tab: Show Secondary Information',
+ 'tab_info_label' => 'Kartica: Prikaži dodatnu informaciju',
'tab_content' => 'Sadržaj',
- 'tab_content_label' => 'Tab: Show Primary Content',
+ 'tab_content_label' => 'Kartica: Prikaži glavni sadržaj',
// Email Content
'email_action_help' => 'Ukoliko imate poteškoća sa pritiskom na ":actionText" dugme, kopirajte i zaljepite URL koji se nalazi ispod u vaš web pretraživač:',
'export_html' => 'Sadržani web fajl',
'export_pdf' => 'PDF fajl',
'export_text' => 'Plain Text fajl',
+ 'export_md' => 'Markdown File',
// Permissions and restrictions
'permissions' => 'Dozvole',
'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' => 'Copy Permissions to Books',
'shelves_copy_permissions' => 'Copy Permissions',
'shelves_copy_permissions_explain' => 'This will apply the current permission settings of this bookshelf to all books contained within. Before activating, ensure any changes to the permissions of this bookshelf have been saved.',
'recycle_bin' => 'Recycle Bin',
'recycle_bin_desc' => 'Here you can restore items that have been deleted or choose to permanently remove them from the system. This list is unfiltered unlike similar activity lists in the system where permission filters are applied.',
'recycle_bin_deleted_item' => 'Deleted Item',
+ 'recycle_bin_deleted_parent' => 'Parent',
'recycle_bin_deleted_by' => 'Deleted By',
'recycle_bin_deleted_at' => 'Deletion Time',
'recycle_bin_permanently_delete' => 'Permanently Delete',
'recycle_bin_restore_list' => 'Items to be Restored',
'recycle_bin_restore_confirm' => 'This action will restore the deleted item, including any child elements, to their original location. If the original location has since been deleted, and is now in the recycle bin, the parent item will also need to be restored.',
'recycle_bin_restore_deleted_parent' => 'The parent of this item has also been deleted. These will remain deleted until that parent is also restored.',
+ 'recycle_bin_restore_parent' => 'Restore Parent',
'recycle_bin_destroy_notification' => 'Deleted :count total items from the recycle bin.',
'recycle_bin_restore_notification' => 'Restored :count total items from the recycle bin.',
'audit_table_user' => 'User',
'audit_table_event' => 'Event',
'audit_table_related' => 'Related Item or Detail',
+ 'audit_table_ip' => 'IP Address',
'audit_table_date' => 'Activity Date',
'audit_date_from' => 'Date Range From',
'audit_date_to' => 'Date Range To',
'role_details' => 'Role Details',
'role_name' => 'Role Name',
'role_desc' => 'Short Description of Role',
+ 'role_mfa_enforced' => 'Requires Multi-Factor Authentication',
'role_external_auth_id' => 'External Authentication IDs',
'role_system' => 'System Permissions',
'role_manage_users' => 'Manage users',
'role_manage_page_templates' => 'Manage page templates',
'role_access_api' => 'Access system API',
'role_manage_settings' => 'Manage app settings',
+ 'role_export_content' => 'Export content',
'role_asset' => 'Asset Permissions',
'roles_system_warning' => 'Be aware that access to any of the above three permissions can allow a user to alter their own privileges or the privileges of others in the system. Only assign roles with these permissions to trusted users.',
'role_asset_desc' => 'These permissions control default access to the assets within the system. Permissions on Books, Chapters and Pages will override these permissions.',
'users_api_tokens_create' => 'Create Token',
'users_api_tokens_expires' => 'Expires',
'users_api_tokens_docs' => 'API Documentation',
+ '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',
// API Tokens
'user_api_token_create' => 'Create API Token',
'it' => 'Italian',
'ja' => '日本語',
'ko' => '한국어',
+ 'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nl' => 'Nederlands',
'nb' => 'Norsk (Bokmål)',
'alpha_dash' => ':attribute može sadržavati samo slova, brojeve, crtice i donje crtice.',
'alpha_num' => ':attribute može sadržavati samo slova i brojeve.',
'array' => ':attribute mora biti niz.',
+ 'backup_codes' => 'The provided code is not valid or has already been used.',
'before' => ':attribute mora biti datum prije :date.',
'between' => [
'numeric' => ':attribute mora biti između :min i :max.',
],
'string' => ':attribute mora biti string.',
'timezone' => ':attribute mora biti ispravna zona.',
+ 'totp' => 'The provided code is not valid or has expired.',
'unique' => ':attribute je zauzet.',
'url' => 'Format :attribute je neispravan.',
'uploaded' => 'Fajl nije učitan. Server ne prihvata fajlove ove veličine.',
'favourite_add_notification' => '":name" has been added to your favourites',
'favourite_remove_notification' => '":name" has been removed from your favourites',
+ // MFA
+ 'mfa_setup_method_notification' => 'Multi-factor method successfully configured',
+ 'mfa_remove_method_notification' => 'Multi-factor method successfully removed',
+
// Other
'commented_on' => 'ha comentat a',
'permissions_update' => 'ha actualitzat els permisos',
'user_invite_page_welcome' => 'Us donem la benvinguda a :appName!',
'user_invite_page_text' => 'Per a enllestir el vostre compte i obtenir-hi accés, cal que definiu una contrasenya, que es farà servir per a iniciar la sessió a :appName en futures visites.',
'user_invite_page_confirm_button' => 'Confirma la contrasenya',
- 'user_invite_success' => 'S\'ha establert la contrasenya, ara ja teniu accés a :appName!'
+ 'user_invite_success' => 'S\'ha establert la contrasenya, ara ja teniu accés a :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_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.',
];
\ No newline at end of file
'reset' => 'Reinicialitza',
'remove' => 'Elimina',
'add' => 'Afegeix',
+ 'configure' => 'Configure',
'fullscreen' => 'Pantalla completa',
'favourite' => 'Favourite',
'unfavourite' => 'Unfavourite',
'no_activity' => 'No hi ha activitat',
'no_items' => 'No hi ha cap element',
'back_to_top' => 'Torna a dalt',
+ 'skip_to_main_content' => 'Skip to main content',
'toggle_details' => 'Commuta els detalls',
'toggle_thumbnails' => 'Commuta les miniatures',
'details' => 'Detalls',
'export_html' => 'Fitxer web independent',
'export_pdf' => 'Fitxer PDF',
'export_text' => 'Fitxer de text sense format',
+ 'export_md' => 'Markdown File',
// Permissions and restrictions
'permissions' => 'Permisos',
'shelves_permissions' => 'Permisos del prestatge',
'shelves_permissions_updated' => 'S\'han actualitzat els permisos del prestatge',
'shelves_permissions_active' => 'S\'han activat els permisos del prestatge',
+ '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' => 'Copia els permisos als llibres',
'shelves_copy_permissions' => 'Copia els permisos',
'shelves_copy_permissions_explain' => 'Això aplicarà la configuració de permisos actual d\'aquest prestatge a tots els llibres que contingui. Abans d\'activar-ho, assegureu-vos que hàgiu desat qualsevol canvi als permisos d\'aquest prestatge.',
'recycle_bin' => 'Paperera de reciclatge',
'recycle_bin_desc' => 'Aquí podeu restaurar els elements que hàgiu suprimit o triar suprimir-los del sistema de manera permanent. Aquesta llista no té cap filtre, al contrari que altres llistes d\'activitat similars en què es tenen en compte els filtres de permisos.',
'recycle_bin_deleted_item' => 'Element suprimit',
+ 'recycle_bin_deleted_parent' => 'Parent',
'recycle_bin_deleted_by' => 'Suprimit per',
'recycle_bin_deleted_at' => 'Moment de la supressió',
'recycle_bin_permanently_delete' => 'Suprimeix permanentment',
'recycle_bin_restore_list' => 'Elements que es restauraran',
'recycle_bin_restore_confirm' => 'Aquesta acció restaurarà l\'element suprimit, incloent-hi tots els elements fills, a la seva ubicació original. Si la ubicació original ha estat suprimida, i ara és a la paperera de reciclatge, caldrà que també en restaureu l\'element pare.',
'recycle_bin_restore_deleted_parent' => 'El pare d\'aquest element també ha estat suprimit. L\'element es mantindrà suprimit fins que el pare també es restauri.',
+ 'recycle_bin_restore_parent' => 'Restore Parent',
'recycle_bin_destroy_notification' => 'S\'han suprimit :count elements en total de la paperera de reciclatge.',
'recycle_bin_restore_notification' => 'S\'han restaurat :count elements en total de la paperera de reciclatge.',
'audit_table_user' => 'Usuari',
'audit_table_event' => 'Esdeveniment',
'audit_table_related' => 'Element relacionat o detall',
+ 'audit_table_ip' => 'IP Address',
'audit_table_date' => 'Data de l\'activitat',
'audit_date_from' => 'Rang de dates a partir de',
'audit_date_to' => 'Rang de rates fins a',
'role_details' => 'Detalls del rol',
'role_name' => 'Nom del rol',
'role_desc' => 'Descripció curta del rol',
+ 'role_mfa_enforced' => 'Requires Multi-Factor Authentication',
'role_external_auth_id' => 'Identificadors d\'autenticació externa',
'role_system' => 'Permisos del sistema',
'role_manage_users' => 'Gestiona usuaris',
'role_manage_page_templates' => 'Gestiona les plantilles de pàgines',
'role_access_api' => 'Accedeix a l\'API del sistema',
'role_manage_settings' => 'Gestiona la configuració de l\'aplicació',
+ 'role_export_content' => 'Export content',
'role_asset' => 'Permisos de recursos',
'roles_system_warning' => 'Tingueu en compte que l\'accés a qualsevol dels tres permisos de dalt pot permetre que un usuari alteri els seus propis permisos o els privilegis d\'altres usuaris del sistema. Assigneu rols amb aquests permisos només a usuaris de confiança.',
'role_asset_desc' => 'Aquests permisos controlen l\'accés per defecte als recursos del sistema. Els permisos de llibres, capítols i pàgines tindran més importància que aquests permisos.',
'users_api_tokens_create' => 'Crea un testimoni',
'users_api_tokens_expires' => 'Caducitat',
'users_api_tokens_docs' => 'Documentació de l\'API',
+ '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',
// API Tokens
'user_api_token_create' => 'Crea un testimoni d\'API',
'it' => 'Italian',
'ja' => '日本語',
'ko' => '한국어',
+ 'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nl' => 'Nederlands',
'nb' => 'Norsk (Bokmål)',
'alpha_dash' => 'El camp :attribute només pot contenir lletres, números, guions i guions baixos.',
'alpha_num' => 'El camp :attribute només pot contenir lletres i números.',
'array' => 'El camp :attribute ha de ser un vector.',
+ 'backup_codes' => 'The provided code is not valid or has already been used.',
'before' => 'El camp :attribute ha de ser una data anterior a :date.',
'between' => [
'numeric' => 'El camp :attribute ha d\'estar entre :min i :max.',
],
'string' => 'El camp :attribute ha de ser una cadena.',
'timezone' => 'El camp :attribute ha de ser una zona vàlida.',
+ 'totp' => 'The provided code is not valid or has expired.',
'unique' => 'El camp :attribute ja està ocupat.',
'url' => 'El format del camp :attribute no és vàlid.',
'uploaded' => 'No s\'ha pogut pujar el fitxer. És possible que el servidor no accepti fitxers d\'aquesta mida.',
'page_update' => 'aktualizoval/a stránku',
'page_update_notification' => 'Stránka byla úspěšně aktualizována',
'page_delete' => 'odstranil/a stránku',
- 'page_delete_notification' => 'Stránka byla úspěšně smazána',
+ 'page_delete_notification' => 'Stránka byla odstraněna',
'page_restore' => 'obnovil/a stránku',
'page_restore_notification' => 'Stránka byla úspěšně obnovena',
'page_move' => 'přesunul/a stránku',
'chapter_create_notification' => 'Kapitola byla úspěšně vytvořena',
'chapter_update' => 'aktualizoval/a kapitolu',
'chapter_update_notification' => 'Kapitola byla úspěšně aktualizována',
- 'chapter_delete' => 'smazal/a kapitolu',
- 'chapter_delete_notification' => 'Kapitola byla úspěšně smazána',
+ 'chapter_delete' => 'odstranila/a kapitolu',
+ 'chapter_delete_notification' => 'Kapitola byla odstraněna',
'chapter_move' => 'přesunul/a kapitolu',
// Books
'book_create' => 'vytvořil/a knihu',
- 'book_create_notification' => 'Kniha byla úspěšně vytvořena',
+ 'book_create_notification' => 'Kniha byla vytvořena',
'book_update' => 'aktualizoval/a knihu',
- 'book_update_notification' => 'Kniha byla úspěšně aktualizována',
- 'book_delete' => 'smazal/a knihu',
- 'book_delete_notification' => 'Kniha byla úspěšně smazána',
+ 'book_update_notification' => 'Kniha byla aktualizována',
+ 'book_delete' => 'odstranil/a knihu',
+ 'book_delete_notification' => 'Kniha byla odstraněna',
'book_sort' => 'seřadil/a knihu',
- 'book_sort_notification' => 'Kniha byla úspěšně seřazena',
+ 'book_sort_notification' => 'Kniha byla seřazena',
// Bookshelves
'bookshelf_create' => 'vytvořil/a knihovnu',
'bookshelf_update' => 'aktualizoval/a knihovnu',
'bookshelf_update_notification' => 'Knihovna byla úspěšně aktualizována',
'bookshelf_delete' => 'odstranil/a knihovnu',
- 'bookshelf_delete_notification' => 'Knihovna byla úspěšně odstraněna',
+ 'bookshelf_delete_notification' => 'Knihovna byla odstraněna',
// Favourites
- 'favourite_add_notification' => '":name" has been added to your favourites',
- 'favourite_remove_notification' => '":name" has been removed from your favourites',
+ 'favourite_add_notification' => '":name" byla přidána do Vašich oblíbených',
+ 'favourite_remove_notification' => '":name" byla odstraněna z Vašich oblíbených',
+
+ // MFA
+ 'mfa_setup_method_notification' => 'Multi-factor method successfully configured',
+ 'mfa_remove_method_notification' => 'Multi-factor method successfully removed',
// Other
'commented_on' => 'okomentoval/a',
- 'permissions_update' => 'updated permissions',
+ 'permissions_update' => 'oprávnění upravena',
];
*/
return [
- 'failed' => 'Tyto přihlašovací údaje neodpovídají našim záznamům.',
+ 'failed' => 'Neplatné přihlašovací údaje.',
'throttle' => 'Příliš mnoho pokusů o přihlášení. Zkuste to prosím znovu za :seconds sekund.',
// Login & Register
'sign_up' => 'Registrace',
'log_in' => 'Přihlášení',
- 'log_in_with' => 'Přihlásit se pomocí :socialDriver',
- 'sign_up_with' => 'Registrovat se pomocí :socialDriver',
+ 'log_in_with' => 'Přihlásit se přes :socialDriver',
+ 'sign_up_with' => 'Registrovat se přes :socialDriver',
'logout' => 'Odhlásit',
'name' => 'Jméno',
'username' => 'Uživatelské jméno',
'email' => 'E-mail',
'password' => 'Heslo',
- 'password_confirm' => 'Potvrdit heslo',
- 'password_hint' => 'Musí mít více než 7 znaků',
- 'forgot_password' => 'Zapomněli jste heslo?',
+ 'password_confirm' => 'Potvrzení hesla',
+ 'password_hint' => 'Musí mít víc než 7 znaků',
+ 'forgot_password' => 'Zapomenuté heslo?',
'remember_me' => 'Zapamatovat si mě',
'ldap_email_hint' => 'Zadejte email, který chcete přiřadit k tomuto účtu.',
'create_account' => 'Vytvořit účet',
'already_have_account' => 'Již máte účet?',
- 'dont_have_account' => 'Nemáte účet?',
- 'social_login' => 'Přihlášení pomocí sociálních sítí',
- 'social_registration' => 'Přihlášení pomocí sociálních sítí',
- 'social_registration_text' => 'Registrovat a přihlásit se pomocí jiné služby.',
+ 'dont_have_account' => 'Nemáte učet?',
+ 'social_login' => 'Přihlášení přes sociální sítě',
+ 'social_registration' => 'Registrace přes sociální sítě',
+ 'social_registration_text' => 'Registrovat a přihlásit se přes jinou službu',
'register_thanks' => 'Děkujeme za registraci!',
'register_confirm' => 'Zkontrolujte prosím svůj e-mail a klikněte na potvrzovací tlačítko pro přístup do :appName.',
- 'registrations_disabled' => 'Registrace jsou aktuálně zakázány',
- 'registration_email_domain_invalid' => 'Tato e-mailová doména nemá přístup k této aplikaci',
+ 'registrations_disabled' => 'Registrace jsou momentálně pozastaveny',
+ 'registration_email_domain_invalid' => 'Registrace z této e-mailové domény nejsou povoleny',
'register_success' => 'Děkujeme za registraci! Nyní jste zaregistrováni a přihlášeni.',
// Password Reset
'reset_password' => 'Obnovit heslo',
- 'reset_password_send_instructions' => 'Níže zadejte svou e-mailovou adresu a bude vám zaslán e-mail s odkazem pro obnovení hesla.',
- 'reset_password_send_button' => 'Zaslat odkaz pro obnovení',
+ 'reset_password_send_instructions' => 'Níže zadejte svou e-mailovou adresu a bude vám zaslán e-mail s odkazem na obnovení hesla.',
+ 'reset_password_send_button' => 'Zaslat odkaz na obnovení hesla',
'reset_password_sent' => 'Odkaz pro obnovení hesla bude odeslán na :email, pokud bude tato e-mailová adresa nalezena v systému.',
- 'reset_password_success' => 'Vaše heslo bylo úspěšně obnoveno.',
+ 'reset_password_success' => 'Vaše heslo bylo obnoveno.',
'email_reset_subject' => 'Obnovit heslo do :appName',
'email_reset_text' => 'Tento e-mail jste obdrželi, protože jsme obdrželi žádost o obnovení hesla k vašemu účtu.',
'email_reset_not_requested' => 'Pokud jste o obnovení hesla nežádali, není vyžadována žádná další akce.',
'email_not_confirmed_resend_button' => 'Znovu odeslat potvrzovací e-mail',
// User Invite
- 'user_invite_email_subject' => 'Byli jste pozváni přidat se do :appName!',
+ 'user_invite_email_subject' => 'Byli jste pozváni do :appName!',
'user_invite_email_greeting' => 'Byl pro vás vytvořen účet na :appName.',
'user_invite_email_text' => 'Klikněte na níže uvedené tlačítko pro nastavení hesla k účtu a získání přístupu:',
'user_invite_email_action' => 'Nastavit heslo k účtu',
'user_invite_page_welcome' => 'Vítejte v :appName!',
- 'user_invite_page_text' => 'Pro dokončení vašeho účtu a získání přístupu musíte nastavit heslo, které bude použito k přihlášení do :appName při budoucích návštěvách.',
+ 'user_invite_page_text' => 'Pro dokončení vašeho účtu a získání přístupu musíte nastavit heslo, které bude použito k přihlášení do :appName při dalších návštěvách.',
'user_invite_page_confirm_button' => 'Potvrdit heslo',
- 'user_invite_success' => 'Heslo nastaveno, nyní máte přístup k :appName!'
+ 'user_invite_success' => 'Heslo nastaveno, nyní máte přístup k :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_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.',
];
\ No newline at end of file
'reset' => 'Obnovit',
'remove' => 'Odebrat',
'add' => 'Přidat',
+ 'configure' => 'Configure',
'fullscreen' => 'Celá obrazovka',
- 'favourite' => 'Favourite',
- 'unfavourite' => 'Unfavourite',
- 'next' => 'Next',
- 'previous' => 'Previous',
+ 'favourite' => 'Přidat do oblíbených',
+ 'unfavourite' => 'Odebrat z oblíbených',
+ 'next' => 'Další',
+ 'previous' => 'Předchozí',
// Sort Options
'sort_options' => 'Možnosti řazení',
'sort_ascending' => 'Řadit vzestupně',
'sort_descending' => 'Řadit sestupně',
'sort_name' => 'Název',
- 'sort_default' => 'Default',
+ 'sort_default' => 'Výchozí',
'sort_created_at' => 'Datum vytvoření',
'sort_updated_at' => 'Datum aktualizace',
'no_activity' => 'Žádná aktivita k zobrazení',
'no_items' => 'Žádné položky k dispozici',
'back_to_top' => 'Zpět na začátek',
+ 'skip_to_main_content' => 'Přeskočit na hlavní obsah',
'toggle_details' => 'Přepnout podrobnosti',
'toggle_thumbnails' => 'Přepnout náhledy',
'details' => 'Podrobnosti',
'breadcrumb' => 'Drobečková navigace',
// Header
- 'header_menu_expand' => 'Expand Header Menu',
+ 'header_menu_expand' => 'Rozbalit menu v záhlaví',
'profile_menu' => 'Nabídka profilu',
'view_profile' => 'Zobrazit profil',
'edit_profile' => 'Upravit profil',
// Layout tabs
'tab_info' => 'Informace',
- 'tab_info_label' => 'Tab: Show Secondary Information',
+ 'tab_info_label' => 'Tab: Zobrazit podružné informace',
'tab_content' => 'Obsah',
- 'tab_content_label' => 'Tab: Show Primary Content',
+ 'tab_content_label' => 'Tab: Zobrazit hlavní obsah',
// Email Content
'email_action_help' => 'Pokud se vám nedaří kliknout na tlačítko „:actionText“, zkopírujte a vložte níže uvedenou URL do vašeho webového prohlížeče:',
// Footer Link Options
// Not directly used but available for convenience to users.
- 'privacy_policy' => 'Privacy Policy',
- 'terms_of_service' => 'Terms of Service',
+ 'privacy_policy' => 'Zásady ochrany osobních údajů',
+ 'terms_of_service' => 'Podmínky služby',
];
'image_image_name' => 'Název obrázku',
'image_delete_used' => 'Tento obrázek je použit na níže uvedených stránkách.',
'image_delete_confirm_text' => 'Opravdu chcete odstranit tento obrázek?',
- 'image_select_image' => 'Vyberte obrázek',
+ 'image_select_image' => 'Zvolte obrázek',
'image_dropzone' => 'Přetáhněte obrázky nebo klikněte sem pro nahrání',
'images_deleted' => 'Obrázky odstraněny',
'image_preview' => 'Náhled obrázku',
- 'image_upload_success' => 'Obrázek byl úspěšně nahrán',
- 'image_update_success' => 'Podrobnosti o obrázku byly úspěšně aktualizovány',
- 'image_delete_success' => 'Obrázek byl úspěšně odstraněn',
+ 'image_upload_success' => 'Obrázek byl nahrán',
+ 'image_update_success' => 'Podrobnosti o obrázku byly aktualizovány',
+ 'image_delete_success' => 'Obrázek byl odstraněn',
'image_upload_remove' => 'Odebrat',
// Code Editor
'recently_update' => 'Nedávno aktualizované',
'recently_viewed' => 'Nedávno zobrazené',
'recent_activity' => 'Nedávné aktivity',
- 'create_now' => 'Vytvořte ji nyní',
+ 'create_now' => 'Vytvořit nyní',
'revisions' => 'Revize',
'meta_revision' => 'Revize č. :revisionCount',
'meta_created' => 'Vytvořeno :timeLength',
'meta_created_name' => 'Vytvořeno :timeLength uživatelem :user',
'meta_updated' => 'Aktualizováno :timeLength',
'meta_updated_name' => 'Aktualizováno :timeLength uživatelem :user',
- 'meta_owned_name' => 'Owned by :user',
+ 'meta_owned_name' => 'Vlastník :user',
'entity_select' => 'Výběr entity',
'images' => 'Obrázky',
'my_recent_drafts' => 'Mé nedávné koncepty',
'my_recently_viewed' => 'Mé nedávno zobrazené',
- 'my_most_viewed_favourites' => 'My Most Viewed Favourites',
- 'my_favourites' => 'My Favourites',
+ 'my_most_viewed_favourites' => 'Mé nejčastěji zobrazené oblíbené',
+ 'my_favourites' => 'Mé oblíbené',
'no_pages_viewed' => 'Nezobrazili jste žádné stránky',
- 'no_pages_recently_created' => 'Nedávno nebyly vytvořeny žádné stránky',
- 'no_pages_recently_updated' => 'Nedávno nebyly aktualizovány žádné stránky',
+ 'no_pages_recently_created' => 'Žádné nedávno vytvořené stránky',
+ 'no_pages_recently_updated' => 'Žádné nedávno aktualizované stránky',
'export' => 'Exportovat',
- 'export_html' => 'Konsolidovaný webový soubor',
- 'export_pdf' => 'Soubor PDF',
+ 'export_html' => 'HTML stránka s celým obsahem',
+ 'export_pdf' => 'PDF dokument',
'export_text' => 'Textový soubor',
+ 'export_md' => 'Markdown',
// 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_save' => 'Uložit oprávnění',
- 'permissions_owner' => 'Owner',
+ 'permissions_owner' => 'Vlastník',
// Search
'search_results' => 'Výsledky hledání',
- 'search_total_results_found' => 'Nalezen :count výsledek|Nalezeny :count výsledky|Nalezeny :count výsledky|Nalezeny :count výsledky|Nalezeno :count výsledků',
+ 'search_total_results_found' => '{1}Nalezen :count výsledek|[2,4]Nalezeny :count výsledky|[5,*]Nalezeno :count výsledků',
'search_clear' => 'Vymazat hledání',
'search_no_pages' => 'Tomuto hledání neodpovídají žádné stránky',
'search_for_term' => 'Hledat :term',
'search_permissions_set' => 'Sada oprávnění',
'search_created_by_me' => 'Vytvořeno mnou',
'search_updated_by_me' => 'Aktualizováno mnou',
- 'search_owned_by_me' => 'Owned by me',
+ 'search_owned_by_me' => 'Patřící mně',
'search_date_options' => 'Možnosti data',
'search_updated_before' => 'Aktualizováno před',
'search_updated_after' => 'Aktualizováno po',
'shelves_new' => 'Nové knihovny',
'shelves_new_action' => 'Nová Knihovna',
'shelves_popular_empty' => 'Nejpopulárnější knihovny se objeví zde.',
- 'shelves_new_empty' => 'Zde se objeví nejnověji vytvořené knihovny.',
+ 'shelves_new_empty' => 'Zde se zobrazí nejnověji vytvořené knihovny.',
'shelves_save' => 'Uložit knihovnu',
'shelves_books' => 'Knihy v této knihovně',
'shelves_add_books' => 'Přidat knihy do knihovny',
- 'shelves_drag_books' => 'Knihu přidáte jejím přetažením sem.',
+ 'shelves_drag_books' => 'Knihu přidáte jejím přetažením sem',
'shelves_empty_contents' => 'Tato knihovna neobsahuje žádné knihy',
- 'shelves_edit_and_assign' => 'Pro přidáni knih do knihovny stiskněte úprvy.',
+ 'shelves_edit_and_assign' => 'Upravit knihovnu a přiřadit knihy',
'shelves_edit_named' => 'Upravit knihovnu :name',
'shelves_edit' => 'Upravit knihovnu',
'shelves_delete' => 'Odstranit knihovnu',
'shelves_delete_named' => 'Odstranit knihovnu :name',
- 'shelves_delete_explain' => "Toto odstraní knihovnu s názvem ‚:name‘. Obsažené knihy nebudou odstraněny.",
+ 'shelves_delete_explain' => "Toto odstraní knihovnu ‚:name‘. Vložené knihy nebudou odstraněny.",
'shelves_delete_confirmation' => 'Opravdu chcete odstranit tuto knihovnu?',
'shelves_permissions' => 'Oprávnění knihovny',
'shelves_permissions_updated' => 'Oprávnění knihovny byla aktualizována',
- 'shelves_permissions_active' => 'Oprávnění knihovny jsou aktivní',
+ 'shelves_permissions_active' => 'Oprávnění knihovny byla aktivována',
+ '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' => 'Kopírovat oprávnění na knihy',
'shelves_copy_permissions' => 'Kopírovat oprávnění',
- 'shelves_copy_permissions_explain' => 'Toto použije aktuální nastavení oprávnění této knihovny na všechny knihy v ní obsažené. Před aktivací se ujistěte, že byly uloženy všechny změny oprávnění této knihovny.',
+ 'shelves_copy_permissions_explain' => 'Toto použije aktuální nastavení oprávnění knihovny na všechny knihy v ní obsažené. Před aktivací se ujistěte, že byly uloženy všechny změny oprávnění této knihovny.',
'shelves_copy_permission_success' => 'Oprávnění knihovny byla zkopírována na :count knih',
// Books
'books_recent' => 'Nedávné knihy',
'books_new' => 'Nové knihy',
'books_new_action' => 'Nová kniha',
- 'books_popular_empty' => 'Zde se objeví nejoblíbenější knihy.',
- 'books_new_empty' => 'Zde se objeví nejnověji vytvořené knihy.',
+ 'books_popular_empty' => 'Zde se zobrazí nejoblíbenější knihy.',
+ 'books_new_empty' => 'Zde se zobrazí nejnověji vytvořené knihy.',
'books_create' => 'Vytvořit novou knihu',
'books_delete' => 'Odstranit knihu',
'books_delete_named' => 'Odstranit knihu :bookName',
- 'books_delete_explain' => 'Toto odstraní knihu s názvem ‚:bookName‘. Všechny stránky a kapitoly budou odebrány.',
+ 'books_delete_explain' => 'Toto odstraní knihu ‚:bookName‘. Všechny stránky a kapitoly v této knize budou také odstraněny.',
'books_delete_confirmation' => 'Opravdu chcete odstranit tuto knihu?',
'books_edit' => 'Upravit knihu',
'books_edit_named' => 'Upravit knihu :bookName',
'books_form_book_name' => 'Název knihy',
'books_save' => 'Uložit knihu',
'books_permissions' => 'Oprávnění knihy',
- 'books_permissions_updated' => 'Oprávnění knihy aktualizována',
- 'books_empty_contents' => 'Pro tuto knihu nebyly vytvořeny žádné stránky nebo kapitoly.',
+ 'books_permissions_updated' => 'Oprávnění knihy byla aktualizována',
+ 'books_empty_contents' => 'Pro tuto knihu nebyly vytvořeny žádné stránky ani kapitoly.',
'books_empty_create_page' => 'Vytvořit novou stránku',
'books_empty_sort_current_book' => 'Seřadit aktuální knihu',
'books_empty_add_chapter' => 'Přidat kapitolu',
- 'books_permissions_active' => 'Oprávnění knihy jsou aktivní',
+ 'books_permissions_active' => 'Oprávnění knihy byla aktivována',
'books_search_this' => 'Prohledat tuto knihu',
'books_navigation' => 'Navigace knihy',
'books_sort' => 'Seřadit obsah knihy',
'books_sort_name' => 'Seřadit podle názvu',
'books_sort_created' => 'Seřadit podle data vytvoření',
'books_sort_updated' => 'Seřadit podle data aktualizace',
- 'books_sort_chapters_first' => 'Kapitoly první',
- 'books_sort_chapters_last' => 'Kapitoly poslední',
+ 'books_sort_chapters_first' => 'Kapitoly jako první',
+ 'books_sort_chapters_last' => 'Kapitoly jako poslední',
'books_sort_show_other' => 'Zobrazit ostatní knihy',
'books_sort_save' => 'Uložit nové pořadí',
'chapters_popular' => 'Populární kapitoly',
'chapters_new' => 'Nová kapitola',
'chapters_create' => 'Vytvořit novou kapitolu',
- 'chapters_delete' => 'Smazat kapitolu',
- 'chapters_delete_named' => 'Smazat kapitolu :chapterName',
- 'chapters_delete_explain' => 'This will delete the chapter with the name \':chapterName\'. All pages that exist within this chapter will also be deleted.',
- 'chapters_delete_confirm' => 'Opravdu chcete tuto kapitolu smazat?',
+ 'chapters_delete' => 'Odstranit kapitolu',
+ 'chapters_delete_named' => 'Odstranit kapitolu :chapterName',
+ 'chapters_delete_explain' => 'Toto odstraní kapitolu ‚:chapterName‘. Všechny stránky v této kapitole budou také odstraněny.',
+ 'chapters_delete_confirm' => 'Opravdu chcete odstranit tuto kapitolu?',
'chapters_edit' => 'Upravit kapitolu',
'chapters_edit_named' => 'Upravit kapitolu :chapterName',
'chapters_save' => 'Uložit kapitolu',
'chapters_move' => 'Přesunout kapitolu',
'chapters_move_named' => 'Přesunout kapitolu :chapterName',
'chapter_move_success' => 'Kapitola přesunuta do knihy :bookName',
- 'chapters_permissions' => 'Práva kapitoly',
+ 'chapters_permissions' => 'Oprávnění kapitoly',
'chapters_empty' => 'Tato kapitola neobsahuje žádné stránky',
- 'chapters_permissions_active' => 'Účinná práva kapitoly',
- 'chapters_permissions_success' => 'Práva kapitoly aktualizována',
+ 'chapters_permissions_active' => 'Oprávnění kapitoly byla aktivována',
+ 'chapters_permissions_success' => 'Oprávnění kapitoly byla aktualizována',
'chapters_search_this' => 'Prohledat tuto kapitolu',
// Pages
'pages_new' => 'Nová stránka',
'pages_attachments' => 'Přílohy',
'pages_navigation' => 'Obsah stránky',
- 'pages_delete' => 'Smazat stránku',
- 'pages_delete_named' => 'Smazat stránku :pageName',
- 'pages_delete_draft_named' => 'Smazat koncept stránky :pageName',
+ 'pages_delete' => 'Odstranit stránku',
+ 'pages_delete_named' => 'Odstranit stránku :pageName',
+ 'pages_delete_draft_named' => 'Odstranit koncept stránky :pageName',
'pages_delete_draft' => 'Odstranit koncept stránky',
'pages_delete_success' => 'Stránka odstraněna',
'pages_delete_draft_success' => 'Koncept stránky odstraněn',
'pages_move_success' => 'Stránka přesunuta do ":parentName"',
'pages_copy' => 'Kopírovat stránku',
'pages_copy_desination' => 'Cíl kopírování',
- 'pages_copy_success' => 'Stránka byla úspěšně zkopírována',
+ 'pages_copy_success' => 'Stránka byla zkopírována',
'pages_permissions' => 'Oprávnění stránky',
- 'pages_permissions_success' => 'Oprávnění stránky aktualizována',
+ 'pages_permissions_success' => 'Oprávnění stránky byla aktualizována',
'pages_revision' => 'Revize',
'pages_revisions' => 'Revize stránky',
'pages_revisions_named' => 'Revize stránky pro :pageName',
'pages_revision_named' => 'Revize stránky pro :pageName',
- 'pages_revision_restored_from' => 'Restored from #:id; :summary',
+ 'pages_revision_restored_from' => 'Obnoveno z #:id; :summary',
'pages_revisions_created_by' => 'Vytvořeno uživatelem',
'pages_revisions_date' => 'Datum revize',
- 'pages_revisions_number' => 'Č.',
+ 'pages_revisions_number' => 'Č. ',
'pages_revisions_numbered' => 'Revize č. :id',
'pages_revisions_numbered_changes' => 'Změny revize č. :id',
'pages_revisions_changelog' => 'Protokol změn',
'pages_revisions_none' => 'Tato stránka nemá žádné revize',
'pages_copy_link' => 'Kopírovat odkaz',
'pages_edit_content_link' => 'Upravit obsah',
- 'pages_permissions_active' => 'Účinná práva stránky',
+ 'pages_permissions_active' => 'Oprávnění stránky byla aktivována',
'pages_initial_revision' => 'První vydání',
'pages_initial_name' => 'Nová stránka',
'pages_editing_draft_notification' => 'Právě upravujete koncept, který byl uložen před :timeDiff.',
'attachments_link' => 'Připojit odkaz',
'attachments_set_link' => 'Nastavit odkaz',
'attachments_delete' => 'Jste si jisti, že chcete odstranit tuto přílohu?',
- 'attachments_dropzone' => 'Přetáhněte sem soubory myší nebo sem kliknětě pro vybrání souboru.',
- 'attachments_no_files' => 'Žádné soubory nebyli nahrány',
+ 'attachments_dropzone' => 'Přetáhněte sem soubory myší nebo sem klikněte pro vybrání souboru',
+ 'attachments_no_files' => 'Žádné soubory nebyly nahrány',
'attachments_explain_link' => 'Můžete pouze připojit odkaz pokud nechcete nahrávat soubor přímo. Může to být odkaz na jinou stránku nebo na soubor v cloudu.',
'attachments_link_name' => 'Název odkazu',
'attachment_link' => 'Odkaz na přílohu',
'attachments_insert_link' => 'Přidat odkaz na přílohu do stránky',
'attachments_edit_file' => 'Upravit soubor',
'attachments_edit_file_name' => 'Název souboru',
- 'attachments_edit_drop_upload' => 'Přetáhněte sem soubor myší nebo klikněte pro nahrání nového a následné přepsání starého.',
+ 'attachments_edit_drop_upload' => 'Přetáhněte sem soubor myší nebo klikněte pro nahrání nového souboru a následné přepsání starého',
'attachments_order_updated' => 'Pořadí příloh aktualizováno',
'attachments_updated_success' => 'Podrobnosti příloh aktualizovány',
- 'attachments_deleted' => 'Příloha byla smazána',
- 'attachments_file_uploaded' => 'Soubor byl úspěšně nahrán',
- 'attachments_file_updated' => 'Soubor byl úspěšně aktualizován',
- 'attachments_link_attached' => 'Odkaz úspěšně přiložen ke stránce',
+ 'attachments_deleted' => 'Příloha byla odstraněna',
+ 'attachments_file_uploaded' => 'Soubor byl nahrán',
+ 'attachments_file_updated' => 'Soubor byl aktualizován',
+ 'attachments_link_attached' => 'Odkaz byl přiložen ke stránce',
'templates' => 'Šablony',
'templates_set_as_template' => 'Tato stránka je šablona',
'templates_explain_set_as_template' => 'Tuto stránku můžete nastavit jako šablonu, aby byl její obsah využit při vytváření dalších stránek. Ostatní uživatelé budou moci použít tuto šablonu, pokud mají oprávnění k zobrazení této stránky.',
'comment' => 'Komentář',
'comments' => 'Komentáře',
'comment_add' => 'Přidat komentář',
- 'comment_placeholder' => 'Zanechat komentář zde',
+ 'comment_placeholder' => 'Zde zadejte komentář',
'comment_count' => '{0} Bez komentářů|{1} 1 komentář|[2,4] :count komentáře|[5,*] :count komentářů',
'comment_save' => 'Uložit komentář',
'comment_saving' => 'Ukládání komentáře...',
'comment_new' => 'Nový komentář',
'comment_created' => 'komentováno :createDiff',
'comment_updated' => 'Aktualizováno :updateDiff uživatelem :username',
- 'comment_deleted_success' => 'Komentář smazán',
+ 'comment_deleted_success' => 'Komentář odstraněn',
'comment_created_success' => 'Komentář přidán',
'comment_updated_success' => 'Komentář aktualizován',
- 'comment_delete_confirm' => 'Opravdu chcete smazat tento komentář?',
+ 'comment_delete_confirm' => 'Opravdu chcete odstranit tento komentář?',
'comment_in_reply_to' => 'Odpověď na :commentId',
// Revision
- 'revision_delete_confirm' => 'Opravdu chcete smazat tuto revizi?',
+ 'revision_delete_confirm' => 'Opravdu chcete odstranit tuto revizi?',
'revision_restore_confirm' => 'Jste si jisti, že chcete obnovit tuto revizi? Aktuální obsah stránky bude nahrazen.',
- 'revision_delete_success' => 'Revize smazána',
- 'revision_cannot_delete_latest' => 'Nelze smazat poslední revizi.'
+ 'revision_delete_success' => 'Revize odstraněna',
+ 'revision_cannot_delete_latest' => 'Nelze odstranit poslední revizi.'
];
return [
// Permissions
- 'permission' => 'Nemáte povolení přistupovat na dotazovanou stránku.',
+ 'permission' => 'Nemáte povolení přistupovat na požadovanou stránku.',
'permissionJson' => 'Nemáte povolení k provedení požadované akce.',
// Auth
'error_user_exists_different_creds' => 'Uživatel s emailem :email již existuje ale s jinými přihlašovacími údaji.',
'email_already_confirmed' => 'Emailová adresa již byla potvrzena. Zkuste se přihlásit.',
'email_confirmation_invalid' => 'Tento potvrzovací odkaz již neplatí nebo už byl použit. Zkuste prosím registraci znovu.',
- 'email_confirmation_expired' => 'Potvrzovací odkaz už neplatí, email s novým odkazem už byl poslán.',
+ 'email_confirmation_expired' => 'Tento potvrzovací odkaz již neplatí, byl Vám odeslán nový potvrzovací e-mail.',
'email_confirmation_awaiting' => 'E-mailová adresa pro používaný účet musí být potvrzena',
'ldap_fail_anonymous' => 'Přístup k adresáři LDAP jako anonymní uživatel (anonymous bind) selhal',
'ldap_fail_authed' => 'Přístup k adresáři LDAP pomocí zadaného jména (dn) a hesla selhal',
// Pages
'page_draft_autosave_fail' => 'Nepovedlo se uložit koncept. Než stránku uložíte, ujistěte se, že jste připojeni k internetu.',
- 'page_custom_home_deletion' => 'Nelze smazat tuto stránku, protože je nastavena jako uvítací stránka.',
+ 'page_custom_home_deletion' => 'Nelze odstranit tuto stránku, protože je nastavena jako uvítací stránka',
// Entities
'entity_not_found' => 'Prvek nenalezen',
'chapter_not_found' => 'Kapitola nenalezena',
'selected_book_not_found' => 'Vybraná kniha nebyla nalezena',
'selected_book_chapter_not_found' => 'Zvolená kniha nebo kapitola nebyla nalezena',
- 'guests_cannot_save_drafts' => 'Návštěvníci z řad veřejnosti nemohou ukládat koncepty.',
+ 'guests_cannot_save_drafts' => 'Nepřihlášení návštěvníci nemohou ukládat koncepty',
// Users
- 'users_cannot_delete_only_admin' => 'Nemůžete smazat posledního administrátora',
- 'users_cannot_delete_guest' => 'Uživatele host není možno smazat',
+ 'users_cannot_delete_only_admin' => 'Nemůžete odstranit posledního administrátora',
+ 'users_cannot_delete_guest' => 'Uživatele Host není možno odstranit',
// Roles
'role_cannot_be_edited' => 'Tuto roli nelze editovat',
- 'role_system_cannot_be_deleted' => 'Toto je systémová role a nelze jí smazat.',
- 'role_registration_default_cannot_delete' => 'Tuto roli nelze smazat dokud je nastavená jako výchozí role pro registraci nových uživatelů.',
+ 'role_system_cannot_be_deleted' => 'Toto je systémová role a nelze jí odstranit',
+ 'role_registration_default_cannot_delete' => 'Tuto roli nelze odstranit dokud je nastavená jako výchozí role pro registraci nových uživatelů',
'role_cannot_remove_only_admin' => 'Tento uživatel má roli administrátora. Přiřaďte roli administrátora někomu jinému než jí odeberete zde.',
// Comments
- 'comment_list' => 'Při dotahování komentářů nastala chyba.',
+ 'comment_list' => 'Při načítání komentářů nastala chyba.',
'cannot_add_comment_to_draft' => 'Nemůžete přidávat komentáře ke konceptu.',
'comment_add' => 'Při přidávání / aktualizaci komentáře nastala chyba.',
- 'comment_delete' => 'Při mazání komentáře nastala chyba.',
+ 'comment_delete' => 'Při odstraňování komentáře nastala chyba.',
'empty_comment' => 'Nemůžete přidat prázdný komentář.',
// Error pages
'404_page_not_found' => 'Stránka nenalezena',
- 'sorry_page_not_found' => 'Omlouváme se, ale stránka, kterou hledáte nebyla nalezena.',
+ 'sorry_page_not_found' => 'Omlouváme se, ale stránka, kterou hledáte, nebyla nalezena.',
'sorry_page_not_found_permission_warning' => 'Pokud očekáváte, že by stránka měla existovat, možná jen nemáte oprávnění pro její zobrazení.',
- 'image_not_found' => 'Image Not Found',
- 'image_not_found_subtitle' => 'Sorry, The image file you were looking for could not be found.',
- 'image_not_found_details' => 'If you expected this image to exist it might have been deleted.',
+ 'image_not_found' => 'Obrázek nenalezen',
+ 'image_not_found_subtitle' => 'Omlouváme se, ale obrázek, který hledáte, nebyl nalezen.',
+ 'image_not_found_details' => 'Pokud očekáváte, že by obrázel měl existovat, tak byl zřejmě již odstraněn.',
'return_home' => 'Návrat domů',
'error_occurred' => 'Nastala chyba',
'app_down' => ':appName je momentálně vypnutá',
- 'back_soon' => 'Brzy naběhne.',
+ 'back_soon' => 'Brzy bude opět v provozu.',
// API errors
- 'api_no_authorization_found' => 'V požadavku nebyla nalezen žádný autorizační token',
+ 'api_no_authorization_found' => 'V požadavku nebyl nalezen žádný autorizační token',
'api_bad_authorization_format' => 'V požadavku byl nalezen autorizační token, ale jeho formát se zdá být chybný',
'api_user_token_not_found' => 'Pro zadaný autorizační token nebyl nalezen žádný odpovídající API token',
'api_incorrect_token_secret' => 'Poskytnutý Token Secret neodpovídá použitému API tokenu',
*/
return [
- 'password' => 'Heslo musí mít alespoň osm znaků a musí odpovídat potvrzení.',
+ 'password' => 'Heslo musí mít alespoň osm znaků a musí odpovídat potvrzení hesla.',
'user' => "Nemůžeme nalézt uživatele s touto e-mailovou adresou.",
- 'token' => 'Token pro obnovení hesla je neplatný pro tuto e-mailovou adresu.',
- 'sent' => 'Poslali jsme vám e-mail s odkazem pro obnovení hesla!',
+ 'token' => 'Token pro obnovení hesla není platný pro tuto e-mailovou adresu.',
+ 'sent' => 'Poslali jsme Vám e-mail s odkazem pro obnovení hesla!',
'reset' => 'Vaše heslo bylo obnoveno!',
];
'app_primary_color' => 'Hlavní barva aplikace',
'app_primary_color_desc' => 'Nastaví hlavní barvu aplikace včetně panelů, tlačítek a odkazů.',
'app_homepage' => 'Úvodní stránka aplikace',
- 'app_homepage_desc' => 'Vyberte si zobrazení, které se použije jako úvodní stránka. U zvolených stránek bude ignorováno jejich oprávnění.',
+ 'app_homepage_desc' => 'Zvolte si zobrazení, které se použije jako úvodní stránka. U zvolených stránek bude ignorováno jejich oprávnění.',
'app_homepage_select' => 'Zvolte stránku',
- 'app_footer_links' => 'Footer Links',
- 'app_footer_links_desc' => 'Add links to show within the site footer. These will be displayed at the bottom of most pages, including those that do not require login. You can use a label of "trans::<key>" to use system-defined translations. For example: Using "trans::common.privacy_policy" will provide the translated text "Privacy Policy" and "trans::common.terms_of_service" will provide the translated text "Terms of Service".',
- 'app_footer_links_label' => 'Link Label',
- 'app_footer_links_url' => 'Link URL',
- 'app_footer_links_add' => 'Add Footer Link',
+ 'app_footer_links' => 'Odkazy v zápatí',
+ 'app_footer_links_desc' => 'Přidejte odkazy, které se zobrazí v zápatí webu. Ty se zobrazí ve spodní části většiny stránek, včetně těch, které nevyžadují přihlášení. K použití překladů definovaných systémem můžete použít štítek „trans::<key>“. Například: Použití „trans::common.privacy_policy“ přeloží text na „Zásady ochrany osobních údajů“ a „trans::common.terms_of_service“ poskytne přeložený text „Podmínky služby“.',
+ 'app_footer_links_label' => 'Text odkazu',
+ 'app_footer_links_url' => 'URL odkazu',
+ 'app_footer_links_add' => 'Přidat odkaz do zápatí',
'app_disable_comments' => 'Vypnutí komentářů',
'app_disable_comments_toggle' => 'Vypnout komentáře',
'app_disable_comments_desc' => 'Vypne komentáře napříč všemi stránkami. <br> Existující komentáře se přestanou zobrazovat.',
'maint' => 'Údržba',
'maint_image_cleanup' => 'Pročistění obrázků',
'maint_image_cleanup_desc' => "Prohledá stránky a jejich revize, aby zjistil, které obrázky a kresby jsou momentálně používány a které jsou zbytečné. Zajistěte plnou zálohu databáze a obrázků než se do toho pustíte.",
- 'maint_delete_images_only_in_revisions' => 'Also delete images that only exist in old page revisions',
+ 'maint_delete_images_only_in_revisions' => 'Odstranit i obrázky, které se vyskytují pouze ve starých revizích stránky',
'maint_image_cleanup_run' => 'Spustit pročištění',
- 'maint_image_cleanup_warning' => 'Nalezeno :count potenciálně nepoužitých obrázků. Jste si jistí, že je chcete smazat?',
- 'maint_image_cleanup_success' => 'Potenciálně nepoužité obrázky byly smazány. Celkem :count.',
+ 'maint_image_cleanup_warning' => 'Nalezeno :count potenciálně nepoužitých obrázků. Jste si jisti, že je chcete odstranit?',
+ 'maint_image_cleanup_success' => 'Nalezeno :count potenciálně nepoužitých obrázků a všechny byly odstraněny!',
'maint_image_cleanup_nothing_found' => 'Žádné potenciálně nepoužité obrázky nebyly nalezeny. Nic nebylo smazáno.',
'maint_send_test_email' => 'Odeslat zkušební e-mail',
'maint_send_test_email_desc' => 'Toto pošle zkušební e-mail na vaši e-mailovou adresu uvedenou ve vašem profilu.',
'maint_send_test_email_mail_subject' => 'Testovací e-mail',
'maint_send_test_email_mail_greeting' => 'Zdá se, že posílání e-mailů funguje!',
'maint_send_test_email_mail_text' => 'Gratulujeme! Protože jste dostali tento e-mail, zdá se, že nastavení e-mailů je v pořádku.',
- 'maint_recycle_bin_desc' => 'Deleted shelves, books, chapters & pages are sent to the recycle bin so they can be restored or permanently deleted. Older items in the recycle bin may be automatically removed after a while depending on system configuration.',
- 'maint_recycle_bin_open' => 'Open Recycle Bin',
+ 'maint_recycle_bin_desc' => 'Odstraněné knihovny, knihy, kapitoly a stránky se přesouvají do Koše, aby je bylo možné obnovit nebo trvale smazat. Starší položky v koši mohou být po čase automaticky odstraněny v závislosti na konfiguraci systému.',
+ 'maint_recycle_bin_open' => 'Otevřít Koš',
// Recycle Bin
- 'recycle_bin' => 'Recycle Bin',
- 'recycle_bin_desc' => 'Here you can restore items that have been deleted or choose to permanently remove them from the system. This list is unfiltered unlike similar activity lists in the system where permission filters are applied.',
- 'recycle_bin_deleted_item' => 'Deleted Item',
- 'recycle_bin_deleted_by' => 'Deleted By',
- 'recycle_bin_deleted_at' => 'Deletion Time',
- 'recycle_bin_permanently_delete' => 'Permanently Delete',
- 'recycle_bin_restore' => 'Restore',
- 'recycle_bin_contents_empty' => 'The recycle bin is currently empty',
- 'recycle_bin_empty' => 'Empty Recycle Bin',
- 'recycle_bin_empty_confirm' => 'This will permanently destroy all items in the recycle bin including content contained within each item. Are you sure you want to empty the recycle bin?',
- 'recycle_bin_destroy_confirm' => 'This action will permanently delete this item, along with any child elements listed below, from the system and you will not be able to restore this content. Are you sure you want to permanently delete this item?',
- 'recycle_bin_destroy_list' => 'Items to be Destroyed',
- 'recycle_bin_restore_list' => 'Items to be Restored',
- 'recycle_bin_restore_confirm' => 'This action will restore the deleted item, including any child elements, to their original location. If the original location has since been deleted, and is now in the recycle bin, the parent item will also need to be restored.',
- 'recycle_bin_restore_deleted_parent' => 'The parent of this item has also been deleted. These will remain deleted until that parent is also restored.',
- 'recycle_bin_destroy_notification' => 'Deleted :count total items from the recycle bin.',
- 'recycle_bin_restore_notification' => 'Restored :count total items from the recycle bin.',
+ 'recycle_bin' => 'Koš',
+ 'recycle_bin_desc' => 'Zde můžete obnovit položky, které byly odstraněny, nebo zvolit jejich trvalé odstranění ze systému. Tento seznam je nefiltrovaný, na rozdíl od podobných seznamů aktivit v systému, kde jsou použity filtry podle oprávnění.',
+ 'recycle_bin_deleted_item' => 'Odstraněná položka',
+ 'recycle_bin_deleted_parent' => 'Nadřazená položka',
+ 'recycle_bin_deleted_by' => 'Odstranil/a',
+ 'recycle_bin_deleted_at' => 'Čas odstranění',
+ 'recycle_bin_permanently_delete' => 'Trvale odstranit',
+ 'recycle_bin_restore' => 'Obnovit',
+ 'recycle_bin_contents_empty' => 'Koš je nyní prázdný',
+ 'recycle_bin_empty' => 'Vysypat Koš',
+ 'recycle_bin_empty_confirm' => 'Toto trvale odstraní všechny položky v Koši včetně obsahu vloženého v každé položce. Opravdu chcete vysypat Koš?',
+ 'recycle_bin_destroy_confirm' => 'Tato akce trvale odstraní ze systému tuto položku spolu s veškerým vloženým obsahem a tento obsah nebudete moci obnovit. Opravdu chcete tuto položku trvale odstranit?',
+ 'recycle_bin_destroy_list' => 'Položky k trvalému odstranění',
+ 'recycle_bin_restore_list' => 'Položky k obnovení',
+ 'recycle_bin_restore_confirm' => 'Tato akce obnoví odstraněnou položku včetně veškerého vloženého obsahu do původního umístění. Pokud bylo původní umístění od té doby odstraněno a nyní je v Koši, bude také nutné obnovit nadřazenou položku.',
+ 'recycle_bin_restore_deleted_parent' => 'Nadřazená položka této položky byla také odstraněna. Ty zůstanou odstraněny, dokud nebude obnoven i nadřazený objekt.',
+ 'recycle_bin_restore_parent' => 'Obnovit nadřazenu položku',
+ 'recycle_bin_destroy_notification' => 'Celkem odstraněno :count položek z Koše.',
+ 'recycle_bin_restore_notification' => 'Celkem obnoveno :count položek z Koše.',
// Audit Log
'audit' => 'Protokol auditu',
'audit_deleted_item_name' => 'Jméno: :name',
'audit_table_user' => 'Uživatel',
'audit_table_event' => 'Událost',
- 'audit_table_related' => 'Related Item or Detail',
+ 'audit_table_related' => 'Související položka nebo detail',
+ 'audit_table_ip' => 'IP Address',
'audit_table_date' => 'Datum aktivity',
'audit_date_from' => 'Časový rozsah od',
'audit_date_to' => 'Časový rozsah do',
'roles' => 'Role',
'role_user_roles' => 'Uživatelské role',
'role_create' => 'Vytvořit novou roli',
- 'role_create_success' => 'Role byla úspěšně vytvořena',
- 'role_delete' => 'Smazat roli',
- 'role_delete_confirm' => 'Role \':roleName\' bude smazána.',
+ 'role_create_success' => 'Role byla vytvořena',
+ 'role_delete' => 'Odstranit roli',
+ 'role_delete_confirm' => 'Role \':roleName\' bude odstraněna.',
'role_delete_users_assigned' => 'Role je přiřazena :userCount uživatelům. Pokud jim chcete náhradou přidělit jinou roli, zvolte jednu z následujících.',
'role_delete_no_migration' => "Nepřiřazovat uživatelům náhradní roli",
- 'role_delete_sure' => 'Opravdu chcete tuto roli smazat?',
- 'role_delete_success' => 'Role byla úspěšně smazána',
+ 'role_delete_sure' => 'Opravdu chcete tuto roli odstranit?',
+ 'role_delete_success' => 'Role byla odstraněna',
'role_edit' => 'Upravit roli',
'role_details' => 'Detaily role',
'role_name' => 'Název role',
'role_desc' => 'Stručný popis role',
+ 'role_mfa_enforced' => 'Requires Multi-Factor Authentication',
'role_external_auth_id' => 'Přihlašovací identifikátory třetích stran',
'role_system' => 'Systémová oprávnění',
'role_manage_users' => 'Správa uživatelů',
'role_manage_page_templates' => 'Správa šablon stránek',
'role_access_api' => 'Přístup k systémovému API',
'role_manage_settings' => 'Správa nastavení aplikace',
+ 'role_export_content' => 'Export content',
'role_asset' => 'Obsahová oprávnění',
'roles_system_warning' => 'Berte na vědomí, že přístup k některému ze tří výše uvedených oprávnění může uživateli umožnit změnit svá vlastní oprávnění nebo oprávnění ostatních uživatelů v systému. Přiřazujte role s těmito oprávněními pouze důvěryhodným uživatelům.',
- 'role_asset_desc' => 'Tato práva řídí přístup k obsahu napříč systémem. Specifická práva na knihách, kapitolách a stránkách převáží tato nastavení.',
+ 'role_asset_desc' => 'Tato oprávnění řídí přístup k obsahu napříč systémem. Specifická oprávnění na knihách, kapitolách a stránkách převáží tato nastavení.',
'role_asset_admins' => 'Administrátoři automaticky dostávají přístup k veškerému obsahu, ale tyto volby mohou ukázat nebo skrýt volby v uživatelském rozhraní.',
'role_all' => 'Vše',
'role_own' => 'Vlastní',
'role_controlled_by_asset' => 'Řídí se obsahem, do kterého jsou nahrávány',
'role_save' => 'Uložit roli',
- 'role_update_success' => 'Role úspěšně aktualizována',
+ 'role_update_success' => 'Role byla aktualizována',
'role_users' => 'Uživatelé mající tuto roli',
'role_users_none' => 'Žádný uživatel nemá tuto roli',
'user_profile' => 'Profil uživatele',
'users_add_new' => 'Přidat nového uživatele',
'users_search' => 'Vyhledávání uživatelů',
- 'users_latest_activity' => 'Latest Activity',
+ 'users_latest_activity' => 'Nedávná aktivita',
'users_details' => 'Údaje o uživateli',
'users_details_desc' => 'Nastavte zobrazované jméno a e-mailovou adresu pro tohoto uživatele. E-mailová adresa bude použita pro přihlášení do aplikace.',
'users_details_desc_no_email' => 'Nastavte zobrazované jméno pro tohoto uživatele, aby jej ostatní uživatele poznali.',
'users_system_public' => 'Symbolizuje každého nepřihlášeného návštěvníka, který navštívil aplikaci. Nelze ho použít k přihlášení ale je přiřazen automaticky nepřihlášeným.',
'users_delete' => 'Smazat uživatele',
'users_delete_named' => 'Odstranit uživatele :userName',
- 'users_delete_warning' => 'Uživatel \':userName\' bude zcela smazán ze systému.',
+ 'users_delete_warning' => 'Uživatel \':userName\' bude zcela odstraněn ze systému.',
'users_delete_confirm' => 'Opravdu chcete tohoto uživatele smazat?',
- 'users_migrate_ownership' => 'Migrate Ownership',
- 'users_migrate_ownership_desc' => 'Select a user here if you want another user to become the owner of all items currently owned by this user.',
- 'users_none_selected' => 'No user selected',
- 'users_delete_success' => 'User successfully removed',
+ 'users_migrate_ownership' => 'Převést vlastnictví',
+ 'users_migrate_ownership_desc' => 'Zde zvolte jiného uživatele, pokud chcete, aby se stal vlastníkem všech položek aktuálně vlastněných tímto uživatelem.',
+ 'users_none_selected' => 'Nebyl zvolen žádný uživatel',
+ 'users_delete_success' => 'Uživatel byl odstraněn',
'users_edit' => 'Upravit uživatele',
'users_edit_profile' => 'Upravit profil',
'users_edit_success' => 'Uživatel byl úspěšně aktualizován',
'users_avatar' => 'Obrázek uživatele',
- 'users_avatar_desc' => 'Vyberte obrázek, který bude reprezentovat tohoto uživatele. Měl by být přibližně 256px velký ve tvaru čtverce.',
+ 'users_avatar_desc' => 'Zvolte obrázek, který bude reprezentovat tohoto uživatele. Měl by být přibližně 256px velký ve tvaru čtverce.',
'users_preferred_language' => 'Preferovaný jazyk',
'users_preferred_language_desc' => 'Tato volba ovlivní pouze jazyk používaný v uživatelském rozhraní aplikace. Volba nemá vliv na žádný uživateli vytvářený obsah.',
'users_social_accounts' => 'Sociální účty',
'users_social_accounts_info' => 'Zde můžete přidat vaše účty ze sociálních sítí pro pohodlnější přihlašování. Odpojení účtů neznamená, že tato aplikace ztratí práva číst detaily z vašeho účtu. Zakázat této aplikaci přístup k detailům vašeho účtu musíte přímo ve svém profilu na dané sociální síti.',
'users_social_connect' => 'Připojit účet',
'users_social_disconnect' => 'Odpojit účet',
- 'users_social_connected' => 'Účet :socialAccount byl úspěšně připojen k vašemu profilu.',
- 'users_social_disconnected' => 'Účet :socialAccount byl úspěšně odpojen od vašeho profilu.',
+ 'users_social_connected' => 'Účet :socialAccount byl připojen k vašemu profilu.',
+ 'users_social_disconnected' => 'Účet :socialAccount byl odpojen od vašeho profilu.',
'users_api_tokens' => 'API Tokeny',
'users_api_tokens_none' => 'Tento uživatel nemá vytvořené žádné API Tokeny',
'users_api_tokens_create' => 'Vytvořit Token',
'users_api_tokens_expires' => 'Vyprší',
'users_api_tokens_docs' => 'API Dokumentace',
+ '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',
// API Tokens
- 'user_api_token_create' => 'Vytvořit API Klíč',
+ 'user_api_token_create' => 'Vytvořit API Token',
'user_api_token_name' => 'Název',
'user_api_token_name_desc' => 'Zadejte srozumitelný název tokenu, který vám později může pomoci připomenout účel, za jakým jste token vytvářeli.',
'user_api_token_expiry' => 'Platný do',
'user_api_token_expiry_desc' => 'Zadejte datum, kdy platnost tokenu vyprší. Po tomto datu nebudou požadavky, které používají tento token, fungovat. Pokud ponecháte pole prázdné, bude tokenu nastavena platnost na dalších 100 let.',
'user_api_token_create_secret_message' => 'Ihned po vytvoření tokenu Vám bude vygenerován a zobrazen "Token ID" a "Token Secret". Upozorňujeme, že "Token Secret" bude možné zobrazit pouze jednou, ujistěte se, že si jej poznamenáte a uložíte na bezpečné místo před tím, než budete pokračovat dále.',
- 'user_api_token_create_success' => 'API klíč úspěšně vytvořen',
- 'user_api_token_update_success' => 'API klíč úspěšně aktualizován',
- 'user_api_token' => 'API Klíč',
+ 'user_api_token_create_success' => 'API Token byl vytvořen',
+ 'user_api_token_update_success' => 'API Token byl aktualizován',
+ 'user_api_token' => 'API Token',
'user_api_token_id' => 'Token ID',
- 'user_api_token_id_desc' => 'Toto je neupravitelný systémový identifikátor generovaný pro tento klíč, který musí být uveden v API requestu.',
+ 'user_api_token_id_desc' => 'Toto je neupravitelný systémový identifikátor generovaný pro tento Token, který musí být uveden v API requestu.',
'user_api_token_secret' => 'Token Secret',
- 'user_api_token_secret_desc' => 'Toto je systémem generovaný "secret" pro tento klíč, který musí být v API requestech. Toto bude zobrazeno pouze jednou, takže si uložte tuto hodnotu na bezpečné místo.',
+ 'user_api_token_secret_desc' => 'Toto je systémem generovaný "Secret" pro tento Token, který musí být v API requestech. Toto bude zobrazeno pouze jednou, takže si uložte tuto hodnotu na bezpečné místo.',
'user_api_token_created' => 'Token vytvořen :timeAgo',
'user_api_token_updated' => 'Token aktualizován :timeAgo',
'user_api_token_delete' => 'Odstranit Token',
- 'user_api_token_delete_warning' => 'Tímto plně smažete tento API klíč s názvem \':tokenName\' ze systému.',
- 'user_api_token_delete_confirm' => 'Opravdu chcete odstranit tento API klíč?',
- 'user_api_token_delete_success' => 'API Klíč úspěšně odstraněn',
+ 'user_api_token_delete_warning' => 'Tímto plně odstraníte tento API Token s názvem \':tokenName\' ze systému.',
+ 'user_api_token_delete_confirm' => 'Opravdu chcete odstranit tento API Token?',
+ 'user_api_token_delete_success' => 'API Token byl odstraněn',
//! If editing translations files directly please ignore this in all
//! languages apart from en. Content will be auto-copied from en.
'it' => 'Italian',
'ja' => '日本語',
'ko' => '한국어',
+ 'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nl' => 'Nederlands',
'nb' => 'Norsk (Bokmål)',
'alpha_dash' => ':attribute může obsahovat pouze písmena, číslice, pomlčky a podtržítka. České znaky (á, é, í, ó, ú, ů, ž, š, č, ř, ď, ť, ň) nejsou podporovány.',
'alpha_num' => ':attribute může obsahovat pouze písmena a číslice.',
'array' => ':attribute musí být pole.',
+ 'backup_codes' => 'The provided code is not valid or has already been used.',
'before' => ':attribute musí být datum před :date.',
'between' => [
'numeric' => ':attribute musí být hodnota mezi :min a :max.',
'array' => ':attribute by měl obsahovat méně než :value položek.',
],
'lte' => [
- 'numeric' => ':attribute musí být menší nebo rovno než :value.',
+ 'numeric' => ':attribute musí být menší nebo rovno :value.',
'file' => 'Velikost souboru :attribute musí být menší než :value kB.',
'string' => ':attribute nesmí být delší než :value znaků.',
'array' => ':attribute by měl obsahovat maximálně :value položek.',
'required_without' => ':attribute musí být vyplněno pokud :values není vyplněno.',
'required_without_all' => ':attribute musí být vyplněno pokud není žádné z :values zvoleno.',
'same' => ':attribute a :other se musí shodovat.',
- 'safe_url' => 'The provided link may not be safe.',
+ 'safe_url' => 'Zadaný odkaz může být nebezpečný.',
'size' => [
'numeric' => ':attribute musí být přesně :size.',
'file' => ':attribute musí mít přesně :size Kilobytů.',
],
'string' => ':attribute musí být řetězec znaků.',
'timezone' => ':attribute musí být platná časová zóna.',
+ 'totp' => 'The provided code is not valid or has expired.',
'unique' => ':attribute musí být unikátní.',
'url' => 'Formát :attribute je neplatný.',
'uploaded' => 'Nahrávání :attribute se nezdařilo.',
'bookshelf_delete_notification' => 'Bogreolen blev opdateret',
// Favourites
- 'favourite_add_notification' => '":name" has been added to your favourites',
- 'favourite_remove_notification' => '":name" has been removed from your favourites',
+ 'favourite_add_notification' => '":name" er blevet tilføjet til dine favoritter',
+ 'favourite_remove_notification' => '":name" er blevet fjernet fra dine favoritter',
+
+ // MFA
+ 'mfa_setup_method_notification' => 'Multi-faktor metode konfigureret',
+ 'mfa_remove_method_notification' => 'Multi-faktor metode fjernet',
// Other
'commented_on' => 'kommenterede til',
'user_invite_page_welcome' => 'Velkommen til :appName!',
'user_invite_page_text' => 'For at færdiggøre din konto og få adgang skal du indstille en adgangskode, der bruges til at logge ind på :appName ved fremtidige besøg.',
'user_invite_page_confirm_button' => 'Bekræft adgangskode',
- 'user_invite_success' => 'Adgangskode indstillet, du har nu adgang til :appName!'
+ 'user_invite_success' => 'Adgangskode indstillet, du har nu adgang til :appName!',
+
+ // Multi-factor Authentication
+ 'mfa_setup' => 'Opsætning af Multi-faktor godkendelse',
+ 'mfa_setup_desc' => 'Opsæt multi-faktor godkendelse som et ekstra lag af sikkerhed for din brugerkonto.',
+ 'mfa_setup_configured' => 'Allerede konfigureret',
+ 'mfa_setup_reconfigure' => 'Genkonfigurer',
+ 'mfa_setup_remove_confirmation' => 'Er du sikker på, at du vil fjerne denne multi-faktor godkendelsesmetode?',
+ 'mfa_setup_action' => 'Opsætning',
+ 'mfa_backup_codes_usage_limit_warning' => 'Du har mindre end 5 backup koder tilbage, generere og gem et nyt sæt før du løber tør for koder, for at forhindre at blive lukket ude af din konto.',
+ 'mfa_option_totp_title' => 'Mobil app',
+ 'mfa_option_totp_desc' => 'For at bruge multi-faktor godkendelse, skal du bruge en mobil app, der understøtter TOTP såsom Google Authenticator, Authy eller Microsoft Authenticator.',
+ 'mfa_option_backup_codes_title' => 'Backup koder',
+ 'mfa_option_backup_codes_desc' => 'Gem sikkert et sæt af engangs backup koder, som du kan indtaste for at bekræfte din identitet.',
+ 'mfa_gen_confirm_and_enable' => 'Bekræft og aktivér',
+ 'mfa_gen_backup_codes_title' => 'Backup koder opsætning',
+ '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.',
];
\ No newline at end of file
'reset' => 'Nulstil',
'remove' => 'Fjern',
'add' => 'Tilføj',
+ 'configure' => 'Konfigurer',
'fullscreen' => 'Fuld skærm',
- 'favourite' => 'Favourite',
- 'unfavourite' => 'Unfavourite',
- 'next' => 'Next',
- 'previous' => 'Previous',
+ 'favourite' => 'Foretrukken',
+ 'unfavourite' => 'Fjern som foretrukken',
+ 'next' => 'Næste',
+ 'previous' => 'Forrige',
// Sort Options
'sort_options' => 'Sorteringsindstillinger',
'no_activity' => 'Ingen aktivitet at vise',
'no_items' => 'Intet indhold tilgængeligt',
'back_to_top' => 'Tilbage til toppen',
+ 'skip_to_main_content' => 'Spring til indhold',
'toggle_details' => 'Vis/skjul detaljer',
'toggle_thumbnails' => 'Vis/skjul miniaturer',
'details' => 'Detaljer',
'images' => 'Billeder',
'my_recent_drafts' => 'Mine seneste kladder',
'my_recently_viewed' => 'Mine senest viste',
- 'my_most_viewed_favourites' => 'My Most Viewed Favourites',
- 'my_favourites' => 'My Favourites',
+ 'my_most_viewed_favourites' => 'Mine mest viste favoritter',
+ 'my_favourites' => 'Mine favoritter',
'no_pages_viewed' => 'Du har ikke besøgt nogle sider',
'no_pages_recently_created' => 'Ingen sider er blevet oprettet for nyligt',
'no_pages_recently_updated' => 'Ingen sider er blevet opdateret for nyligt',
'export_html' => 'Indeholdt webfil',
'export_pdf' => 'PDF-fil',
'export_text' => 'Almindelig tekstfil',
+ 'export_md' => 'Markdown Fil',
// Permissions and restrictions
'permissions' => 'Rettigheder',
'shelves_permissions' => 'Reoltilladelser',
'shelves_permissions_updated' => 'Reoltilladelser opdateret',
'shelves_permissions_active' => 'Aktive reoltilladelser',
+ 'shelves_permissions_cascade_warning' => 'Tilladelser på reoler nedarves ikke automatisk til indeholdte bøger. Dette skyldes, at en bog kan eksistere på flere hylder. Tilladelser kan dog kopieres ned til underliggende bøger ved hjælp af muligheden, der findes nedenfor.',
'shelves_copy_permissions_to_books' => 'Kopier tilladelser til bøger',
'shelves_copy_permissions' => 'Kopier tilladelser',
'shelves_copy_permissions_explain' => 'Dette vil anvende de aktuelle tilladelsesindstillinger på denne boghylde på alle bøger indeholdt i. Før aktivering skal du sikre dig, at ændringer i tilladelserne til denne boghylde er blevet gemt.',
'404_page_not_found' => 'Siden blev ikke fundet',
'sorry_page_not_found' => 'Beklager, siden du leder efter blev ikke fundet.',
'sorry_page_not_found_permission_warning' => 'Hvis du forventede, at denne side skulle eksistere, har du muligvis ikke tilladelse til at se den.',
- 'image_not_found' => 'Image Not Found',
- 'image_not_found_subtitle' => 'Sorry, The image file you were looking for could not be found.',
- 'image_not_found_details' => 'If you expected this image to exist it might have been deleted.',
+ 'image_not_found' => 'Billede ikke fundet',
+ 'image_not_found_subtitle' => 'Beklager, billedet du ledte efter kunne ikke findes.',
+ 'image_not_found_details' => 'Hvis du forventede, at dette billede skulle eksistere, kan det være blevet slettet.',
'return_home' => 'Gå tilbage til hjem',
'error_occurred' => 'Der opstod en fejl',
'app_down' => ':appName er nede lige nu',
'recycle_bin' => 'Papirkurv',
'recycle_bin_desc' => 'Her kan du gendanne elementer, der er blevet slettet eller vælge at permanent fjerne dem fra systemet. Denne liste er ufiltreret, i modsætning til lignende aktivitetslister i systemet, hvor tilladelsesfiltre anvendes.',
'recycle_bin_deleted_item' => 'Slettet element',
+ 'recycle_bin_deleted_parent' => 'Overordnet',
'recycle_bin_deleted_by' => 'Slettet af',
'recycle_bin_deleted_at' => 'Sletningstidspunkt',
'recycle_bin_permanently_delete' => 'Slet permanent',
'recycle_bin_restore_list' => 'Elementer der skal gendannes',
'recycle_bin_restore_confirm' => 'Denne handling vil gendanne det slettede element, herunder alle underelementer, til deres oprindelige placering. Hvis den oprindelige placering siden er blevet slettet, og nu er i papirkurven, vil det overordnede element også skulle gendannes.',
'recycle_bin_restore_deleted_parent' => 'Det overordnede element til dette element er også blevet slettet. Disse vil forblive slettet indtil det overordnede også er gendannet.',
+ 'recycle_bin_restore_parent' => 'Gendan Overordnet',
'recycle_bin_destroy_notification' => 'Slettede :count elementer fra papirkurven.',
'recycle_bin_restore_notification' => 'Gendannede :count elementer fra papirkurven.',
'audit_table_user' => 'Bruger',
'audit_table_event' => 'Hændelse',
'audit_table_related' => 'Relateret element eller detalje',
+ 'audit_table_ip' => 'IP Address',
'audit_table_date' => 'Aktivitetsdato',
'audit_date_from' => 'Datointerval fra',
'audit_date_to' => 'Datointerval til',
'role_details' => 'Rolledetaljer',
'role_name' => 'Rollenavn',
'role_desc' => 'Kort beskrivelse af rolle',
+ 'role_mfa_enforced' => 'Kræver multifaktor godkendelse',
'role_external_auth_id' => 'Eksterne godkendelses-IDer',
'role_system' => 'Systemtilladelser',
'role_manage_users' => 'Administrere brugere',
'role_manage_page_templates' => 'Administrer side-skabeloner',
'role_access_api' => 'Tilgå system-API',
'role_manage_settings' => 'Administrer app-indstillinger',
+ 'role_export_content' => 'Eksporter indhold',
'role_asset' => 'Tilladelser for medier og "assets"',
'roles_system_warning' => 'Vær opmærksom på, at adgang til alle af de ovennævnte tre tilladelser, kan give en bruger mulighed for at ændre deres egne brugerrettigheder eller brugerrettigheder for andre i systemet. Tildel kun roller med disse tilladelser til betroede brugere.',
'role_asset_desc' => 'Disse tilladelser kontrollerer standardadgang til medier og "assets" i systemet. Tilladelser til bøger, kapitler og sider tilsidesætter disse tilladelser.',
'users_api_tokens_create' => 'Opret Token',
'users_api_tokens_expires' => 'Udløber',
'users_api_tokens_docs' => 'API-dokumentation',
+ 'users_mfa' => 'Multi-faktor godkendelse',
+ 'users_mfa_desc' => 'Opsæt multi-faktor godkendelse som et ekstra lag af sikkerhed for din brugerkonto.',
+ 'users_mfa_x_methods' => ':count metode konfigureret|:count metoder konfigureret',
+ 'users_mfa_configure' => 'Konfigurer metoder',
// API Tokens
'user_api_token_create' => 'Opret API-token',
'it' => 'Italian',
'ja' => '日本語',
'ko' => '한국어',
+ 'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nl' => 'Nederlands',
'nb' => 'Norsk (Bokmål)',
'alpha_dash' => ':attribute må kun bestå af bogstaver, tal, binde- og under-streger.',
'alpha_num' => ':attribute må kun indeholde bogstaver og tal.',
'array' => ':attribute skal være et array.',
+ 'backup_codes' => 'Den angivne kode er ikke gyldig eller er allerede brugt.',
'before' => ':attribute skal være en dato før :date.',
'between' => [
'numeric' => ':attribute skal være mellem :min og :max.',
],
'string' => ':attribute skal være tekst.',
'timezone' => ':attribute skal være en gyldig zone.',
+ 'totp' => 'Den angivne kode er ikke gyldig eller er udløbet.',
'unique' => ':attribute er allerede i brug.',
'url' => ':attribute-formatet er ugyldigt.',
'uploaded' => 'Filen kunne ikke oploades. Serveren accepterer muligvis ikke filer af denne størrelse.',
'favourite_add_notification' => '":name" wurde zu deinen Favoriten hinzugefügt',
'favourite_remove_notification' => '":name" wurde aus Ihren Favoriten entfernt',
+ // MFA
+ 'mfa_setup_method_notification' => 'Multi-Faktor-Methode erfolgreich konfiguriert',
+ 'mfa_remove_method_notification' => 'Multi-Faktor-Methode erfolgreich entfernt',
+
// Other
'commented_on' => 'hat einen Kommentar hinzugefügt',
'permissions_update' => 'hat die Berechtigungen aktualisiert',
*/
return [
- 'failed' => 'Die eingegebenen Anmeldedaten sind ungültig.',
+ 'failed' => 'Diese Anmeldedaten stimmen nicht mit unseren Aufzeichnungen überein.',
'throttle' => 'Zu viele Anmeldeversuche. Bitte versuchen Sie es in :seconds Sekunden erneut.',
// Login & Register
'username' => 'Benutzername',
'email' => 'E-Mail',
'password' => 'Passwort',
- 'password_confirm' => 'Passwort bestätigen',
+ 'password_confirm' => 'Passwort bestätigen',
'password_hint' => 'Mindestlänge: 7 Zeichen',
'forgot_password' => 'Passwort vergessen?',
'remember_me' => 'Angemeldet bleiben',
'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_confirm_button' => 'Passwort wiederholen',
- 'user_invite_success' => 'Passwort gesetzt, Sie haben nun Zugriff auf :appName!'
+ 'user_invite_success' => 'Passwort gesetzt, Sie haben nun Zugriff auf :appName!',
+
+ // Multi-factor Authentication
+ 'mfa_setup' => 'Multi-Faktor-Authentifizierung einrichten',
+ 'mfa_setup_desc' => 'Richten Sie Multi-Faktor-Authentifizierung als zusätzliche Sicherheitsstufe für Ihr Benutzerkonto ein.',
+ 'mfa_setup_configured' => 'Bereits konfiguriert',
+ 'mfa_setup_reconfigure' => 'Umkonfigurieren',
+ 'mfa_setup_remove_confirmation' => 'Sind Sie sicher, dass Sie diese Multi-Faktor-Authentifizierungsmethode entfernen möchten?',
+ 'mfa_setup_action' => 'Einrichtung',
+ 'mfa_backup_codes_usage_limit_warning' => 'Sie haben weniger als 5 Backup-Codes übrig, Bitte erstellen und speichern Sie ein neues Set bevor Sie keine Codes mehr haben, um zu verhindern, dass Sie von Ihrem Konto gesperrt werden.',
+ 'mfa_option_totp_title' => 'Mobile App',
+ 'mfa_option_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_option_backup_codes_title' => 'Backup Code',
+ 'mfa_option_backup_codes_desc' => 'Speichern Sie sicher eine Reihe von einmaligen Backup-Codes, die Sie eingeben können, um Ihre Identität zu überprüfen.',
+ 'mfa_gen_confirm_and_enable' => 'Bestätigen und aktivieren',
+ 'mfa_gen_backup_codes_title' => 'Backup-Codes einrichten',
+ 'mfa_gen_backup_codes_desc' => 'Speichern Sie die folgende Liste der Codes an einem sicheren Ort. Wenn Sie auf das System zugreifen, können Sie einen der Codes als zweiten Authentifizierungsmechanismus verwenden.',
+ 'mfa_gen_backup_codes_download' => 'Download Codes',
+ 'mfa_gen_backup_codes_usage_warning' => 'Jeder Code kann nur einmal verwendet werden',
+ 'mfa_gen_totp_title' => 'Mobile App einrichten',
+ '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_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_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_use_totp' => 'Mit einer mobilen App verifizieren',
+ 'mfa_verify_use_backup_codes' => 'Mit einem Backup-Code überprüfen',
+ 'mfa_verify_backup_code' => 'Backup-Code',
+ 'mfa_verify_backup_code_desc' => 'Geben Sie einen Ihrer 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.',
];
\ No newline at end of file
'copy' => 'Kopieren',
'reply' => 'Antworten',
'delete' => 'Löschen',
- 'delete_confirm' => 'Löschen Bestätigen',
+ 'delete_confirm' => 'Löschen bestätigen',
'search' => 'Suchen',
'search_clear' => 'Suche löschen',
'reset' => 'Zurücksetzen',
'remove' => 'Entfernen',
'add' => 'Hinzufügen',
+ 'configure' => 'Konfigurieren',
'fullscreen' => 'Vollbild',
- 'favourite' => 'Favorit',
+ 'favourite' => 'Favoriten',
'unfavourite' => 'Kein Favorit',
'next' => 'Nächste',
'previous' => 'Vorheriges',
'sort_updated_at' => 'Aktualisierungsdatum',
// Misc
- 'deleted_user' => 'Gelöschte Benutzer',
+ 'deleted_user' => 'Gelöschter Benutzer',
'no_activity' => 'Keine Aktivitäten zum Anzeigen',
- 'no_items' => 'Keine Einträge gefunden.',
+ 'no_items' => 'Keine Einträge gefunden',
'back_to_top' => 'nach oben',
+ 'skip_to_main_content' => 'Direkt zum Hauptinhalt',
'toggle_details' => 'Details zeigen/verstecken',
'toggle_thumbnails' => 'Thumbnails zeigen/verstecken',
'details' => 'Details',
'export_html' => 'HTML-Datei',
'export_pdf' => 'PDF-Datei',
'export_text' => 'Textdatei',
+ 'export_md' => 'Markdown-Datei',
// Permissions and restrictions
'permissions' => 'Berechtigungen',
'shelves_permissions' => 'Regal-Berechtigungen',
'shelves_permissions_updated' => 'Regal-Berechtigungen aktualisiert',
'shelves_permissions_active' => 'Regal-Berechtigungen aktiv',
+ 'shelves_permissions_cascade_warning' => 'Die Berechtigungen in Bücherregalen werden nicht automatisch auf enthaltene Bücher kaskadiert, weil ein Buch in mehreren Regalen existieren kann. Berechtigungen können jedoch mit der unten stehenden Option in untergeordnete Bücher kopiert werden.',
'shelves_copy_permissions_to_books' => 'Kopiere die Berechtigungen zum Buch',
'shelves_copy_permissions' => 'Berechtigungen kopieren',
'shelves_copy_permissions_explain' => 'Hiermit werden die Berechtigungen des aktuellen Regals auf alle enthaltenen Bücher übertragen. Überprüfen Sie vor der Aktivierung, ob alle Berechtigungsänderungen am aktuellen Regal gespeichert wurden.',
return [
// Permissions
- 'permission' => 'Sie haben keine Berechtigung, auf diese Seite zuzugreifen.',
+ 'permission' => 'Sie haben keine Zugriffsberechtigung auf die angeforderte Seite.',
'permissionJson' => 'Sie haben keine Berechtigung, die angeforderte Aktion auszuführen.',
// Auth
'email_confirmation_invalid' => 'Der Bestätigungslink ist nicht gültig oder wurde bereits verwendet. Bitte registrieren Sie sich erneut.',
'email_confirmation_expired' => 'Der Bestätigungslink ist abgelaufen. Es wurde eine neue Bestätigungs-E-Mail gesendet.',
'email_confirmation_awaiting' => 'Die E-Mail-Adresse für das verwendete Konto muss bestätigt werden',
- 'ldap_fail_anonymous' => 'Anonymer LDAP-Zugriff ist fehlgeschlafgen',
+ 'ldap_fail_anonymous' => 'Anonymer LDAP-Zugriff ist fehlgeschlagen',
'ldap_fail_authed' => 'LDAP-Zugriff mit DN und Passwort ist fehlgeschlagen',
'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.',
'uploaded' => 'Der Server verbietet das Hochladen von Dateien mit dieser Dateigröße. Bitte versuchen Sie es mit einer kleineren Datei.',
'image_upload_error' => 'Beim Hochladen des Bildes trat ein Fehler auf.',
'image_upload_type_error' => 'Der Bildtyp der hochgeladenen Datei ist ungültig.',
- 'file_upload_timeout' => 'Der Upload der Datei ist abgelaufen.',
+ 'file_upload_timeout' => 'Der Datei-Upload hat das Zeitlimit überschritten.',
// Attachments
'attachment_not_found' => 'Anhang konnte nicht gefunden werden.',
// Pages
'page_draft_autosave_fail' => 'Fehler beim Speichern des Entwurfs. Stellen Sie sicher, dass Sie mit dem Internet verbunden sind, bevor Sie den Entwurf dieser Seite speichern.',
- 'page_custom_home_deletion' => 'Eine als Startseite gesetzte Seite kann nicht gelöscht werden.',
+ 'page_custom_home_deletion' => 'Eine als Startseite gesetzte Seite kann nicht gelöscht werden',
// Entities
'entity_not_found' => 'Eintrag nicht gefunden',
'book_not_found' => 'Buch nicht gefunden',
'page_not_found' => 'Seite nicht gefunden',
'chapter_not_found' => 'Kapitel nicht gefunden',
- 'selected_book_not_found' => 'Das gewählte Buch wurde nicht gefunden.',
+ 'selected_book_not_found' => 'Das gewählte Buch wurde nicht gefunden',
'selected_book_chapter_not_found' => 'Das gewählte Buch oder Kapitel wurde nicht gefunden.',
'guests_cannot_save_drafts' => 'Gäste können keine Entwürfe speichern',
// Users
- 'users_cannot_delete_only_admin' => 'Sie können den einzigen Administrator nicht löschen.',
+ 'users_cannot_delete_only_admin' => 'Sie können den einzigen Administrator nicht löschen',
'users_cannot_delete_guest' => 'Sie können den Gast-Benutzer nicht löschen',
// Roles
- 'role_cannot_be_edited' => 'Diese Rolle kann nicht bearbeitet werden.',
+ 'role_cannot_be_edited' => 'Diese Rolle kann nicht bearbeitet werden',
'role_system_cannot_be_deleted' => 'Dies ist eine Systemrolle und kann nicht gelöscht werden',
'role_registration_default_cannot_delete' => 'Diese Rolle kann nicht gelöscht werden, solange sie als Standardrolle für neue Registrierungen gesetzt ist',
- 'role_cannot_remove_only_admin' => 'Dieser Benutzer ist der einzige Benutzer, welchem die Administratorrolle zugeordnet ist. Ordnen Sie die Administratorrolle einem anderen Benutzer zu, bevor Sie versuchen, sie hier zu entfernen.',
+ 'role_cannot_remove_only_admin' => 'Dieser Benutzer ist der einzige Benutzer, welchem die Administratorrolle zugeordnet ist. Ordnen Sie die Administratorrolle einem anderen Benutzer zu bevor Sie versuchen sie hier zu entfernen.',
// Comments
'comment_list' => 'Beim Abrufen der Kommentare ist ein Fehler aufgetreten.',
'cannot_add_comment_to_draft' => 'Du kannst keine Kommentare zu einem Entwurf hinzufügen.',
'comment_add' => 'Beim Hinzufügen des Kommentars ist ein Fehler aufgetreten.',
'comment_delete' => 'Beim Löschen des Kommentars ist ein Fehler aufgetreten.',
- 'empty_comment' => 'Kann keinen leeren Kommentar hinzufügen',
+ 'empty_comment' => 'Kann keinen leeren Kommentar hinzufügen.',
// Error pages
'404_page_not_found' => 'Seite nicht gefunden',
- 'sorry_page_not_found' => 'Entschuldigung. Die Seite, die Sie angefordert haben, wurde nicht gefunden.',
+ 'sorry_page_not_found' => 'Entschuldigung. Die angeforderte Seite wurde nicht gefunden.',
'sorry_page_not_found_permission_warning' => 'Wenn Sie erwartet haben, dass diese Seite existiert, haben Sie möglicherweise nicht die Berechtigung, sie anzuzeigen.',
'image_not_found' => 'Bild nicht gefunden',
- 'image_not_found_subtitle' => 'Entschuldigung. Das Bild, die Sie angefordert haben, wurde nicht gefunden.',
+ 'image_not_found_subtitle' => 'Entschuldigung. Das angeforderte Bild wurde nicht gefunden.',
'image_not_found_details' => 'Wenn Sie erwartet haben, dass dieses Bild existiert, könnte es gelöscht worden sein.',
'return_home' => 'Zurück zur Startseite',
'error_occurred' => 'Es ist ein Fehler aufgetreten',
- 'app_down' => ':appName befindet sich aktuell im Wartungsmodus.',
+ 'app_down' => ':appName befindet sich aktuell im Wartungsmodus',
'back_soon' => 'Wir werden so schnell wie möglich wieder online sein.',
// API errors
- 'api_no_authorization_found' => 'Kein Autorisierungs-Token für die Anfrage gefunden',
- 'api_bad_authorization_format' => 'Ein Autorisierungs-Token wurde auf die Anfrage gefunden, aber das Format schien falsch zu sein',
- 'api_user_token_not_found' => 'Es wurde kein passender API-Token für den angegebenen Autorisierungs-Token gefunden',
- 'api_incorrect_token_secret' => 'Das für den angegebenen API-Token angegebene Kennwort ist falsch',
- 'api_user_no_api_permission' => 'Der Besitzer des verwendeten API-Token hat keine Berechtigung für API-Aufrufe',
- 'api_user_token_expired' => 'Das verwendete Autorisierungs-Token ist abgelaufen',
+ 'api_no_authorization_found' => 'Kein Autorisierungstoken für die Anfrage gefunden',
+ 'api_bad_authorization_format' => 'Ein Autorisierungstoken wurde auf die Anfrage gefunden, aber das Format schien falsch zu sein',
+ 'api_user_token_not_found' => 'Es wurde kein passender API-Token für den angegebenen Autorisierungstoken gefunden',
+ 'api_incorrect_token_secret' => 'Das Kennwort für das angegebene API-Token ist falsch',
+ 'api_user_no_api_permission' => 'Der Besitzer des verwendeten API-Tokens hat keine Berechtigung für API-Aufrufe',
+ 'api_user_token_expired' => 'Das verwendete Autorisierungstoken ist abgelaufen',
// Settings & Maintenance
- 'maintenance_test_email_failure' => 'Fehler beim Senden einer Test E-Mail:',
+ 'maintenance_test_email_failure' => 'Fehler beim Versenden einer Test E-Mail:',
];
'app_name_desc' => 'Dieser Name wird im Header und in E-Mails angezeigt.',
'app_name_header' => 'Anwendungsname im Header anzeigen?',
'app_public_access' => 'Öffentlicher Zugriff',
- 'app_public_access_desc' => 'Wenn Sie diese Option aktivieren, können Besucher, die nicht angemeldet sind, auf Inhalte in Ihrer BookStack-Instanz zugreifen.',
+ 'app_public_access_desc' => 'Wenn Sie diese Option aktivieren können Besucher, die nicht angemeldet sind, auf Inhalte in Ihrer BookStack-Instanz zugreifen.',
'app_public_access_desc_guest' => 'Der Zugang für öffentliche Besucher kann über den Benutzer "Guest" gesteuert werden.',
'app_public_access_toggle' => 'Öffentlichen Zugriff erlauben',
'app_public_viewing' => 'Öffentliche Ansicht erlauben?',
'app_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::<key>" 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 keinen Login benötigen. Sie können die Bezeichnung "trans::<key>" 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',
// Registration Settings
'reg_settings' => 'Registrierungseinstellungen',
- 'reg_enable' => 'Registrierung erlauben?',
+ 'reg_enable' => 'Registrierung erlauben',
'reg_enable_toggle' => 'Registrierung erlauben',
'reg_enable_desc' => 'Wenn die Registrierung erlaubt ist, kann sich der Benutzer als Anwendungsbenutzer anmelden. Bei der Registrierung erhält er eine einzige, voreingestellte Benutzerrolle.',
'reg_default_role' => 'Standard-Benutzerrolle nach Registrierung',
'recycle_bin' => 'Papierkorb',
'recycle_bin_desc' => 'Hier können Sie gelöschte Elemente wiederherstellen oder sie dauerhaft aus dem System entfernen. Diese Liste ist nicht gefiltert, im Gegensatz zu ähnlichen Aktivitätslisten im System, wo Berechtigungsfilter angewendet werden.',
'recycle_bin_deleted_item' => 'Gelöschtes Element',
+ 'recycle_bin_deleted_parent' => 'Übergeordnet',
'recycle_bin_deleted_by' => 'Gelöscht von',
'recycle_bin_deleted_at' => 'Löschzeitpunkt',
'recycle_bin_permanently_delete' => 'Dauerhaft löschen',
'recycle_bin_restore_list' => 'Zu wiederherzustellende Elemente',
'recycle_bin_restore_confirm' => 'Mit dieser Aktion wird das gelöschte Element einschließlich aller untergeordneten Elemente an seinen ursprünglichen Ort wiederherstellen. Wenn der ursprüngliche Ort gelöscht wurde und sich nun im Papierkorb befindet, muss auch das übergeordnete Element wiederhergestellt werden.',
'recycle_bin_restore_deleted_parent' => 'Das übergeordnete Elements wurde ebenfalls gelöscht. Dieses Element wird weiterhin als gelöscht zählen, bis auch das übergeordnete Element wiederhergestellt wurde.',
+ 'recycle_bin_restore_parent' => 'Übergeordneter Eintrag wiederherstellen',
'recycle_bin_destroy_notification' => ':count Elemente wurden aus dem Papierkorb gelöscht.',
'recycle_bin_restore_notification' => ':count Elemente wurden aus dem Papierkorb wiederhergestellt.',
'audit_table_user' => 'Benutzer',
'audit_table_event' => 'Ereignis',
'audit_table_related' => 'Verknüpftes Element oder Detail',
+ 'audit_table_ip' => 'IP Adresse',
'audit_table_date' => 'Aktivitätsdatum',
'audit_date_from' => 'Zeitraum von',
'audit_date_to' => 'Zeitraum bis',
'role_details' => 'Rollendetails',
'role_name' => 'Rollenname',
'role_desc' => 'Kurzbeschreibung der Rolle',
+ 'role_mfa_enforced' => 'Benötigt Mehrfach-Faktor-Authentifizierung',
'role_external_auth_id' => 'Externe Authentifizierungs-IDs',
'role_system' => 'System-Berechtigungen',
'role_manage_users' => 'Benutzer verwalten',
'role_manage_page_templates' => 'Seitenvorlagen verwalten',
'role_access_api' => 'Systemzugriffs-API',
'role_manage_settings' => 'Globaleinstellungen verwalten',
+ 'role_export_content' => 'Inhalt exportieren',
'role_asset' => 'Berechtigungen',
'roles_system_warning' => 'Beachten Sie, dass der Zugriff auf eine der oben genannten drei Berechtigungen einem Benutzer erlauben kann, seine eigenen Berechtigungen oder die Rechte anderer im System zu ändern. Weisen Sie nur Rollen, mit diesen Berechtigungen, vertrauenswürdigen Benutzern zu.',
'role_asset_desc' => 'Diese Berechtigungen gelten für den Standard-Zugriff innerhalb des Systems. Berechtigungen für Bücher, Kapitel und Seiten überschreiben diese Berechtigungenen.',
'users_api_tokens_create' => 'Token erstellen',
'users_api_tokens_expires' => 'Endet',
'users_api_tokens_docs' => 'API Dokumentation',
+ 'users_mfa' => 'Multi-Faktor-Authentifizierung',
+ 'users_mfa_desc' => 'Richten Sie Multi-Faktor-Authentifizierung als zusätzliche Sicherheitsstufe für Ihr Benutzerkonto ein.',
+ 'users_mfa_x_methods' => ':count Methode konfiguriert|:count Methoden konfiguriert',
+ 'users_mfa_configure' => 'Methoden konfigurieren',
// API Tokens
'user_api_token_create' => 'Neuen API-Token erstellen',
'it' => 'Italian',
'ja' => '日本語',
'ko' => '한국어',
+ 'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nl' => 'Nederlands',
'nb' => 'Norsk (Bokmål)',
'alpha_dash' => ':attribute kann nur Buchstaben, Zahlen und Bindestriche enthalten.',
'alpha_num' => ':attribute kann nur Buchstaben und Zahlen enthalten.',
'array' => ':attribute muss ein Array sein.',
+ 'backup_codes' => 'Der angegebene Code ist ungültig oder wurde bereits verwendet.',
'before' => ':attribute muss ein Datum vor :date sein.',
'between' => [
'numeric' => ':attribute muss zwischen :min und :max liegen.',
],
'string' => ':attribute muss eine Zeichenkette sein.',
'timezone' => ':attribute muss eine valide zeitzone sein.',
+ 'totp' => 'Der angegebene Code ist ungültig oder abgelaufen.',
'unique' => ':attribute wird bereits verwendet.',
'url' => ':attribute ist kein valides Format.',
'uploaded' => 'Die Datei konnte nicht hochgeladen werden. Der Server akzeptiert möglicherweise keine Dateien dieser Größe.',
'favourite_add_notification' => '":name" wurde zu deinen Favoriten hinzugefügt',
'favourite_remove_notification' => '":name" wurde aus Ihren Favoriten entfernt',
+ // MFA
+ 'mfa_setup_method_notification' => 'Multi-factor method successfully configured',
+ 'mfa_remove_method_notification' => 'Multi-factor method successfully removed',
+
// Other
'commented_on' => 'kommentiert',
'permissions_update' => 'aktualisierte Berechtigungen',
'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_confirm_button' => 'Passwort bestätigen',
- 'user_invite_success' => 'Das Passwort wurde gesetzt, du hast nun Zugriff auf :appName!'
+ 'user_invite_success' => 'Das Passwort wurde gesetzt, du hast nun Zugriff auf :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_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.',
];
\ No newline at end of file
'copy' => 'Kopieren',
'reply' => 'Antworten',
'delete' => 'Löschen',
- 'delete_confirm' => 'Löschen Bestätigen',
+ 'delete_confirm' => 'Löschen bestätigen',
'search' => 'Suchen',
'search_clear' => 'Suche löschen',
'reset' => 'Zurücksetzen',
'remove' => 'Entfernen',
'add' => 'Hinzufügen',
+ 'configure' => 'Konfigurieren',
'fullscreen' => 'Vollbild',
- 'favourite' => 'Favorit',
+ 'favourite' => 'Favoriten',
'unfavourite' => 'Kein Favorit',
- 'next' => 'Next',
- 'previous' => 'Previous',
+ 'next' => 'Nächste',
+ 'previous' => 'Vorheriges',
// Sort Options
'sort_options' => 'Sortieroptionen',
'sort_updated_at' => 'Aktualisierungsdatum',
// Misc
- 'deleted_user' => 'Gelöschte Benutzer',
+ 'deleted_user' => 'Gelöschter Benutzer',
'no_activity' => 'Keine Aktivitäten zum Anzeigen',
'no_items' => 'Keine Einträge gefunden.',
'back_to_top' => 'nach oben',
+ 'skip_to_main_content' => 'Direkt zum Hauptinhalt',
'toggle_details' => 'Details zeigen/verstecken',
'toggle_thumbnails' => 'Thumbnails zeigen/verstecken',
'details' => 'Details',
'export_html' => 'HTML-Datei',
'export_pdf' => 'PDF-Datei',
'export_text' => 'Textdatei',
+ 'export_md' => 'Markdown-Dateir',
// Permissions and restrictions
'permissions' => 'Berechtigungen',
'shelves_permissions' => 'Regal-Berechtigungen',
'shelves_permissions_updated' => 'Regal-Berechtigungen aktualisiert',
'shelves_permissions_active' => 'Regal-Berechtigungen aktiv',
+ 'shelves_permissions_cascade_warning' => 'Die Berechtigungen in Bücherregalen werden nicht automatisch auf enthaltene Bücher kaskadiert, weil ein Buch in mehreren Regalen existieren kann. Berechtigungen können jedoch mit der unten stehenden Option in untergeordnete Bücher kopiert werden.',
'shelves_copy_permissions_to_books' => 'Kopiere die Berechtigungen zum Buch',
'shelves_copy_permissions' => 'Berechtigungen kopieren',
'shelves_copy_permissions_explain' => 'Hiermit werden die Berechtigungen des aktuellen Regals auf alle enthaltenen Bücher übertragen. Überprüfe vor der Aktivierung, ob alle Berechtigungsänderungen am aktuellen Regal gespeichert wurden.',
'recycle_bin' => 'Papierkorb',
'recycle_bin_desc' => 'Hier können Sie gelöschte Einträge wiederherstellen oder sie dauerhaft aus dem System entfernen. Diese Liste ist nicht gefiltert, im Gegensatz zu ähnlichen Aktivitätslisten im System, wo Berechtigungsfilter angewendet werden.',
'recycle_bin_deleted_item' => 'Gelöschter Eintrag',
+ 'recycle_bin_deleted_parent' => 'Übergeordnet',
'recycle_bin_deleted_by' => 'Gelöscht von',
'recycle_bin_deleted_at' => 'Löschzeitpunkt',
'recycle_bin_permanently_delete' => 'Dauerhaft löschen',
'recycle_bin_restore_list' => 'Wiederherzustellende Einträge',
'recycle_bin_restore_confirm' => 'Mit dieser Aktion wird der gelöschte Eintrag einschließlich aller untergeordneten Einträge an seinen ursprünglichen Ort wiederherstellen. Wenn der ursprüngliche Ort gelöscht wurde und sich nun im Papierkorb befindet, muss auch der übergeordnete Eintrag wiederhergestellt werden.',
'recycle_bin_restore_deleted_parent' => 'Der übergeordnete Eintrag wurde ebenfalls gelöscht. Dieser Eintrag wird weiterhin als gelöscht zählen, bis auch der übergeordnete Eintrag wiederhergestellt wurde.',
+ 'recycle_bin_restore_parent' => 'Übergeordneter Eintrag wiederherstellen',
'recycle_bin_destroy_notification' => ':count Einträge wurden aus dem Papierkorb gelöscht.',
'recycle_bin_restore_notification' => ':count Einträge wurden aus dem Papierkorb wiederhergestellt.',
'audit_table_user' => 'Benutzer',
'audit_table_event' => 'Ereignis',
'audit_table_related' => 'Verknüpfter Eintrag oder Detail',
+ 'audit_table_ip' => 'IP Address',
'audit_table_date' => 'Aktivitätsdatum',
'audit_date_from' => 'Zeitraum von',
'audit_date_to' => 'Zeitraum bis',
'role_details' => 'Rollendetails',
'role_name' => 'Rollenname',
'role_desc' => 'Kurzbeschreibung der Rolle',
+ 'role_mfa_enforced' => 'Benötigt Mehrfach-Faktor-Authentifizierung',
'role_external_auth_id' => 'Externe Authentifizierungs-IDs',
'role_system' => 'System-Berechtigungen',
'role_manage_users' => 'Benutzer verwalten',
'role_manage_page_templates' => 'Seitenvorlagen verwalten',
'role_access_api' => 'Systemzugriffs-API',
'role_manage_settings' => 'Globaleinstellungen verwalten',
+ 'role_export_content' => 'Inhalt exportieren',
'role_asset' => 'Berechtigungen',
'roles_system_warning' => 'Beachten Sie, dass der Zugriff auf eine der oben genannten drei Berechtigungen einem Benutzer erlauben kann, seine eigenen Berechtigungen oder die Rechte anderer im System zu ändern. Weisen Sie nur Rollen, mit diesen Berechtigungen, vertrauenswürdigen Benutzern zu.',
'role_asset_desc' => 'Diese Berechtigungen gelten für den Standard-Zugriff innerhalb des Systems. Berechtigungen für Bücher, Kapitel und Seiten überschreiben diese Berechtigungenen.',
'users_api_tokens_create' => 'Token erstellen',
'users_api_tokens_expires' => 'Endet',
'users_api_tokens_docs' => 'API Dokumentation',
+ 'users_mfa' => 'Multi-Faktor-Authentifizierung',
+ 'users_mfa_desc' => 'Richte die Multi-Faktor-Authentifizierung als zusätzliche Sicherheitsstufe für dein Benutzerkonto ein.',
+ 'users_mfa_x_methods' => ':count Methode konfiguriert|:count Methoden konfiguriert',
+ 'users_mfa_configure' => 'Methoden konfigurieren',
// API Tokens
'user_api_token_create' => 'Neuen API-Token erstellen',
'it' => 'Italian',
'ja' => '日本語',
'ko' => '한국어',
+ 'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nl' => 'Nederlands',
'nb' => 'Norsk (Bokmål)',
'alpha_dash' => ':attribute kann nur Buchstaben, Zahlen und Bindestriche enthalten.',
'alpha_num' => ':attribute kann nur Buchstaben und Zahlen enthalten.',
'array' => ':attribute muss ein Array sein.',
+ 'backup_codes' => 'The provided code is not valid or has already been used.',
'before' => ':attribute muss ein Datum vor :date sein.',
'between' => [
'numeric' => ':attribute muss zwischen :min und :max liegen.',
],
'string' => ':attribute muss eine Zeichenkette sein.',
'timezone' => ':attribute muss eine valide zeitzone sein.',
+ 'totp' => 'The provided code is not valid or has expired.',
'unique' => ':attribute wird bereits verwendet.',
'url' => ':attribute ist kein valides Format.',
'uploaded' => 'Die Datei konnte nicht hochgeladen werden. Der Server akzeptiert möglicherweise keine Dateien dieser Größe.',
'favourite_add_notification' => '":name" has been added to your favourites',
'favourite_remove_notification' => '":name" has been removed from your favourites',
+ // MFA
+ 'mfa_setup_method_notification' => 'Multi-factor method successfully configured',
+ 'mfa_remove_method_notification' => 'Multi-factor method successfully removed',
+
// Other
'commented_on' => 'commented on',
'permissions_update' => 'updated permissions',
'user_invite_page_welcome' => 'Welcome to :appName!',
'user_invite_page_text' => 'To finalise your account and gain access you need to set a password which will be used to log-in to :appName on future visits.',
'user_invite_page_confirm_button' => 'Confirm Password',
- 'user_invite_success' => 'Password set, you now have access to :appName!'
+ 'user_invite_success' => 'Password set, you now have access to :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_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.',
];
\ No newline at end of file
'reset' => 'Reset',
'remove' => 'Remove',
'add' => 'Add',
+ 'configure' => 'Configure',
'fullscreen' => 'Fullscreen',
'favourite' => 'Favourite',
'unfavourite' => 'Unfavourite',
'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' => 'Copy Permissions to Books',
'shelves_copy_permissions' => 'Copy Permissions',
'shelves_copy_permissions_explain' => 'This will apply the current permission settings of this bookshelf to all books contained within. Before activating, ensure any changes to the permissions of this bookshelf have been saved.',
'audit_table_user' => 'User',
'audit_table_event' => 'Event',
'audit_table_related' => 'Related Item or Detail',
+ 'audit_table_ip' => 'IP Address',
'audit_table_date' => 'Activity Date',
'audit_date_from' => 'Date Range From',
'audit_date_to' => 'Date Range To',
'role_details' => 'Role Details',
'role_name' => 'Role Name',
'role_desc' => 'Short Description of Role',
+ 'role_mfa_enforced' => 'Requires Multi-Factor Authentication',
'role_external_auth_id' => 'External Authentication IDs',
'role_system' => 'System Permissions',
'role_manage_users' => 'Manage users',
'role_manage_page_templates' => 'Manage page templates',
'role_access_api' => 'Access system API',
'role_manage_settings' => 'Manage app settings',
+ 'role_export_content' => 'Export content',
'role_asset' => 'Asset Permissions',
'roles_system_warning' => 'Be aware that access to any of the above three permissions can allow a user to alter their own privileges or the privileges of others in the system. Only assign roles with these permissions to trusted users.',
'role_asset_desc' => 'These permissions control default access to the assets within the system. Permissions on Books, Chapters and Pages will override these permissions.',
'users_api_tokens_create' => 'Create Token',
'users_api_tokens_expires' => 'Expires',
'users_api_tokens_docs' => 'API Documentation',
+ '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',
// API Tokens
'user_api_token_create' => 'Create API Token',
'it' => 'Italian',
'ja' => '日本語',
'ko' => '한국어',
+ 'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nl' => 'Nederlands',
'nb' => 'Norsk (Bokmål)',
'alpha_dash' => 'The :attribute may only contain letters, numbers, dashes and underscores.',
'alpha_num' => 'The :attribute may only contain letters and numbers.',
'array' => 'The :attribute must be an array.',
+ 'backup_codes' => 'The provided code is not valid or has already been used.',
'before' => 'The :attribute must be a date before :date.',
'between' => [
'numeric' => 'The :attribute must be between :min and :max.',
],
'string' => 'The :attribute must be a string.',
'timezone' => 'The :attribute must be a valid zone.',
+ 'totp' => 'The provided code is not valid or has expired.',
'unique' => 'The :attribute has already been taken.',
'url' => 'The :attribute format is invalid.',
'uploaded' => 'The file could not be uploaded. The server may not accept files of this size.',
'favourite_add_notification' => '".name" ha sido añadido a sus favoritos',
'favourite_remove_notification' => '".name" ha sido eliminado de sus favoritos',
+ // MFA
+ 'mfa_setup_method_notification' => 'Método de Autenticación en Dos Pasos configurado correctamente',
+ 'mfa_remove_method_notification' => 'Método de Autenticación en Dos Pasos eliminado correctamente',
+
// Other
'commented_on' => 'comentada el',
'permissions_update' => 'permisos actualizados',
'user_invite_page_welcome' => '¡Bienvenido a :appName!',
'user_invite_page_text' => 'Para completar la cuenta y tener acceso es necesario que configure una contraseña que se utilizará para entrar en :appName en futuros accesos.',
'user_invite_page_confirm_button' => 'Confirmar Contraseña',
- 'user_invite_success' => '¡Contraseña guardada, ya tiene acceso a :appName!'
+ 'user_invite_success' => '¡Contraseña guardada, ya tiene acceso a :appName!',
+
+ // Multi-factor Authentication
+ 'mfa_setup' => 'Configurar Autenticación en Dos Pasos',
+ 'mfa_setup_desc' => 'La autenticación en dos pasos añade una capa de seguridad adicional a tu cuenta de usuario.',
+ 'mfa_setup_configured' => 'Ya está configurado',
+ 'mfa_setup_reconfigure' => 'Reconfigurar',
+ 'mfa_setup_remove_confirmation' => '¿Está seguro de que desea eliminar este método de autenticación en dos pasos?',
+ 'mfa_setup_action' => 'Configuración',
+ 'mfa_backup_codes_usage_limit_warning' => 'Quedan menos de 5 códigos de respaldo, Por favor, genera y almacena un nuevo conjunto antes de que te quedes sin códigos para evitar que te bloquees fuera de tu cuenta.',
+ 'mfa_option_totp_title' => 'Aplicación para móviles',
+ 'mfa_option_totp_desc' => 'Para utilizar la autenticación en dos pasos necesitarás una aplicación móvil que soporte TOTP como Google Authenticator, Authy o Microsoft Authenticator.',
+ 'mfa_option_backup_codes_title' => 'Códigos de Respaldo',
+ 'mfa_option_backup_codes_desc' => 'Almacena de forma segura un conjunto de códigos de respaldo de un solo uso que puedes introducir para verificar tu identidad.',
+ 'mfa_gen_confirm_and_enable' => 'Confirmar y Activar',
+ 'mfa_gen_backup_codes_title' => 'Configuración de Códigos de Respaldo',
+ 'mfa_gen_backup_codes_desc' => 'Guarda la siguiente lista de códigos en un lugar seguro. Al acceder al sistema podrás usar uno de los códigos como un segundo mecanismo de autenticación.',
+ 'mfa_gen_backup_codes_download' => 'Descargar Códigos',
+ 'mfa_gen_backup_codes_usage_warning' => 'Cada código sólo puede utilizarse una vez',
+ 'mfa_gen_totp_title' => 'Configuración de Aplicación móvil',
+ 'mfa_gen_totp_desc' => 'Para utilizar la autenticación en dos pasos necesitarás una aplicación móvil que soporte TOTP como Google Authenticator, Authy o Microsoft Authenticator.',
+ 'mfa_gen_totp_scan' => 'Escanea el código QR mostrado a continuación usando tu aplicación de autenticación preferida para empezar.',
+ 'mfa_gen_totp_verify_setup' => 'Verificar Configuración',
+ 'mfa_gen_totp_verify_setup_desc' => 'Verifica que todo está funcionando introduciendo un código, generado en tu aplicación de autenticación, en el campo de texto a continuación:',
+ 'mfa_gen_totp_provide_code_here' => 'Introduce aquí tu código generado por la aplicación',
+ 'mfa_verify_access' => 'Verificar Acceso',
+ 'mfa_verify_access_desc' => 'Tu cuenta de usuario requiere que confirmes tu identidad a través de un nivel adicional de verificación antes de que te conceda el acceso. Verifica tu identidad usando uno de los métodos configurados para continuar.',
+ 'mfa_verify_no_methods' => 'No hay Métodos Configurados',
+ 'mfa_verify_no_methods_desc' => 'No se han encontrado métodos de autenticación en dos pasos para tu cuenta. Tendrás que configurar al menos un método antes de obtener acceso.',
+ 'mfa_verify_use_totp' => 'Verificar usando una aplicación móvil',
+ 'mfa_verify_use_backup_codes' => 'Verificar usando un código de respaldo',
+ 'mfa_verify_backup_code' => 'Códigos de Respaldo',
+ 'mfa_verify_backup_code_desc' => 'Introduzca uno de sus códigos de respaldo restantes a continuación:',
+ 'mfa_verify_backup_code_enter_here' => 'Introduce el código de respaldo aquí',
+ 'mfa_verify_totp_desc' => 'Introduzca el código, generado con tu aplicación móvil, a continuación:',
+ 'mfa_setup_login_notification' => 'Método de dos factores configurado. Por favor, inicia sesión de nuevo utilizando el método configurado.',
];
\ No newline at end of file
'reset' => 'Resetear',
'remove' => 'Remover',
'add' => 'Añadir',
+ 'configure' => 'Configurar',
'fullscreen' => 'Pantalla completa',
'favourite' => 'Añadir a favoritos',
'unfavourite' => 'Eliminar de favoritos',
'no_activity' => 'Ninguna actividad para mostrar',
'no_items' => 'No hay elementos disponibles',
'back_to_top' => 'Volver arriba',
+ 'skip_to_main_content' => 'Ir al contenido principal',
'toggle_details' => 'Alternar detalles',
'toggle_thumbnails' => 'Alternar miniaturas',
'details' => 'Detalles',
return [
// Shared
- 'recently_created' => 'Recientemente creado',
- 'recently_created_pages' => 'Páginas recientemente creadas',
- 'recently_updated_pages' => 'Páginas recientemente actualizadas',
+ 'recently_created' => 'Creado Recientemente',
+ 'recently_created_pages' => 'Páginas creadas recientemente',
+ 'recently_updated_pages' => 'Páginas actualizadas recientemente',
'recently_created_chapters' => 'Capítulos recientemente creados',
'recently_created_books' => 'Libros recientemente creados',
'recently_created_shelves' => 'Estantes recientemente creados',
'export_html' => 'Archivo web',
'export_pdf' => 'Archivo PDF',
'export_text' => 'Archivo de texto',
+ 'export_md' => 'Archivo Markdown',
// Permissions and restrictions
'permissions' => 'Permisos',
'shelves_permissions' => 'Permisos del estante',
'shelves_permissions_updated' => 'Permisos del estante actualizados',
'shelves_permissions_active' => 'Permisos del estante activos',
+ 'shelves_permissions_cascade_warning' => 'Los permisos en los estantes no se aplican automáticamente a los libros contenidos. Esto se debe a que un libro puede existir en múltiples estantes. Sin embargo, los permisos pueden ser aplicados a los libros del estante utilizando la opción a continuación.',
'shelves_copy_permissions_to_books' => 'Copiar permisos a los libros',
'shelves_copy_permissions' => 'Copiar permisos',
'shelves_copy_permissions_explain' => 'Esto aplicará los ajustes de permisos de este estante para todos sus libros. Antes de activarlo, asegúrese de que todos los cambios de permisos para este estante han sido guardados.',
'recycle_bin' => 'Papelera de Reciclaje',
'recycle_bin_desc' => 'Aquí puede restaurar elementos que hayan sido eliminados o elegir eliminarlos permanentemente del sistema. Esta lista no está filtrada a diferencia de las listas de actividad similares en el sistema donde se aplican los filtros de permisos.',
'recycle_bin_deleted_item' => 'Elemento Eliminado',
+ 'recycle_bin_deleted_parent' => 'Superior',
'recycle_bin_deleted_by' => 'Eliminado por',
'recycle_bin_deleted_at' => 'Fecha de eliminación',
'recycle_bin_permanently_delete' => 'Eliminar permanentemente',
'recycle_bin_restore_list' => 'Elementos a restaurar',
'recycle_bin_restore_confirm' => 'Esta acción restaurará el elemento eliminado, incluyendo cualquier elemento secundario, a su ubicación original. Si la ubicación original ha sido eliminada, y ahora está en la papelera de reciclaje, el elemento padre también tendrá que ser restaurado.',
'recycle_bin_restore_deleted_parent' => 'El padre de este elemento también ha sido eliminado. Estos permanecerán eliminados hasta que el padre también sea restaurado.',
+ 'recycle_bin_restore_parent' => 'Restaurar Superior',
'recycle_bin_destroy_notification' => 'Eliminados :count artículos de la papelera de reciclaje.',
'recycle_bin_restore_notification' => 'Restaurados :count artículos desde la papelera de reciclaje.',
'audit_table_user' => 'Usuario',
'audit_table_event' => 'Evento',
'audit_table_related' => 'Elemento o detalle relacionados',
+ 'audit_table_ip' => 'Dirección IP',
'audit_table_date' => 'Fecha de la actividad',
'audit_date_from' => 'Rango de fecha desde',
'audit_date_to' => 'Rango de fecha hasta',
'role_details' => 'Detalles de rol',
'role_name' => 'Nombre de rol',
'role_desc' => 'Descripción corta de rol',
+ 'role_mfa_enforced' => 'Requiere Autenticación en Dos Pasos',
'role_external_auth_id' => 'ID externo de autenticación',
'role_system' => 'Permisos de sistema',
'role_manage_users' => 'Gestionar usuarios',
'role_manage_page_templates' => 'Administrar plantillas',
'role_access_api' => 'API de sistema de acceso',
'role_manage_settings' => 'Gestionar ajustes de la aplicación',
+ 'role_export_content' => 'Exportar contenido',
'role_asset' => 'Permisos de contenido',
'roles_system_warning' => 'Tenga en cuenta que el acceso a cualquiera de los tres permisos anteriores puede permitir a un usuario alterar sus propios privilegios o los privilegios de otros en el sistema. Sólo asignar roles con estos permisos a usuarios de confianza.',
'role_asset_desc' => 'Estos permisos controlan el acceso por defecto a los contenidos del sistema. Los permisos de Libros, Capítulos y Páginas sobreescribiran estos permisos.',
'users_api_tokens_create' => 'Crear token',
'users_api_tokens_expires' => 'Expira',
'users_api_tokens_docs' => 'Documentación API',
+ 'users_mfa' => 'Autenticación en Dos Pasos',
+ 'users_mfa_desc' => 'La autenticación en dos pasos añade una capa de seguridad adicional a tu cuenta.',
+ 'users_mfa_x_methods' => ':count método configurado|:count métodos configurados',
+ 'users_mfa_configure' => 'Configurar Métodos',
// API Tokens
'user_api_token_create' => 'Crear token API',
'it' => 'Italian',
'ja' => '日本語',
'ko' => '한국어',
+ 'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nl' => 'Nederlands',
'nb' => 'Norsk (Bokmål)',
'alpha_dash' => 'El :attribute solo puede contener letras, números y guiones.',
'alpha_num' => 'El :attribute solo puede contener letras y números.',
'array' => 'El :attribute debe de ser un array.',
+ 'backup_codes' => 'El código suministrado no es válido o ya ha sido utilizado.',
'before' => 'El :attribute debe ser una fecha anterior a :date.',
'between' => [
'numeric' => 'El :attribute debe estar entre :min y :max.',
],
'string' => 'El atributo :attribute debe ser una cadena de texto.',
'timezone' => 'El atributo :attribute debe ser una zona válida.',
+ 'totp' => 'El código suministrado no es válido o ya ha expirado.',
'unique' => 'El atributo :attribute ya ha sido tomado.',
'url' => 'El atributo :attribute tiene un formato inválido.',
'uploaded' => 'El archivo no ha podido subirse. Es posible que el servidor no acepte archivos de este tamaño.',
'favourite_add_notification' => '".name" se añadió a sus favoritos',
'favourite_remove_notification' => '".name" se eliminó de sus favoritos',
+ // MFA
+ 'mfa_setup_method_notification' => 'Método de Autenticación en Dos Pasos configurado correctamente',
+ 'mfa_remove_method_notification' => 'Método de Autenticación en Dos Pasos eliminado correctamente',
+
// Other
'commented_on' => 'comentado',
'permissions_update' => 'permisos actualizados',
'user_invite_page_welcome' => 'Bienvenido a :appName!',
'user_invite_page_text' => 'Para finalizar la cuenta y tener acceso debe establcer una contraseña que utilizará para ingresar a :appName en visitas futuras.',
'user_invite_page_confirm_button' => 'Confirmar Contraseña',
- 'user_invite_success' => 'Contraseña establecida, ahora tiene acceso a :appName!'
+ 'user_invite_success' => 'Contraseña establecida, ahora tiene acceso a :appName!',
+
+ // Multi-factor Authentication
+ 'mfa_setup' => 'Configurar Autenticación en Dos Pasos',
+ 'mfa_setup_desc' => 'La autenticación en dos pasos añade una capa de seguridad adicional a tu cuenta de usuario.',
+ 'mfa_setup_configured' => 'Ya está configurado',
+ 'mfa_setup_reconfigure' => 'Reconfigurar',
+ 'mfa_setup_remove_confirmation' => '¿Está seguro de que desea eliminar este método de autenticación de dos pasos?',
+ 'mfa_setup_action' => 'Configuración',
+ 'mfa_backup_codes_usage_limit_warning' => 'Quedan menos de 5 códigos de respaldo, Por favor, genera y almacena un nuevo conjunto antes de que te quedes sin códigos para evitar que te bloquees fuera de tu cuenta.',
+ 'mfa_option_totp_title' => 'Aplicación para móviles',
+ 'mfa_option_totp_desc' => 'Para utilizar la autenticación de dos pasos necesitarás una aplicación móvil que soporte TOTP como Google Authenticator, Authy o Microsoft Authenticator.',
+ 'mfa_option_backup_codes_title' => 'Códigos de Respaldo',
+ 'mfa_option_backup_codes_desc' => 'Almacena de forma segura un conjunto de códigos de respaldo de un solo uso que puedes introducir para verificar tu identidad.',
+ 'mfa_gen_confirm_and_enable' => 'Confirmar y Activar',
+ 'mfa_gen_backup_codes_title' => 'Configuración de Códigos de Respaldo',
+ 'mfa_gen_backup_codes_desc' => 'Guarda la siguiente lista de códigos en un lugar seguro. Al acceder al sistema podrás usar uno de los códigos como un segundo mecanismo de autenticación.',
+ 'mfa_gen_backup_codes_download' => 'Descargar Códigos',
+ 'mfa_gen_backup_codes_usage_warning' => 'Cada código sólo puede utilizarse una vez',
+ 'mfa_gen_totp_title' => 'Configuración de Aplicación móvil',
+ 'mfa_gen_totp_desc' => 'Para utilizar la autenticación de dos pasos necesitarás una aplicación móvil que soporte TOTP como Google Authenticator, Authy o Microsoft Authenticator.',
+ 'mfa_gen_totp_scan' => 'Escanea el código QR mostrado a continuación usando tu aplicación de autenticación preferida para empezar.',
+ 'mfa_gen_totp_verify_setup' => 'Verificar Configuración',
+ 'mfa_gen_totp_verify_setup_desc' => 'Verifica que todo está funcionando introduciendo un código, generado en tu aplicación de autenticación, en el campo de texto a continuación:',
+ 'mfa_gen_totp_provide_code_here' => 'Introduce aquí tu código generado por la aplicación',
+ 'mfa_verify_access' => 'Verificar Acceso',
+ 'mfa_verify_access_desc' => 'Tu cuenta de usuario requiere que confirmes tu identidad a través de un nivel adicional de verificación antes de que te conceda el acceso. Verifica tu identidad usando uno de los métodos configurados para continuar.',
+ 'mfa_verify_no_methods' => 'No hay Métodos Configurados',
+ 'mfa_verify_no_methods_desc' => 'No se han encontrado métodos de autenticación de dos pasos para tu cuenta. Tendrás que configurar al menos un método antes de obtener acceso.',
+ 'mfa_verify_use_totp' => 'Verificar usando una aplicación móvil',
+ 'mfa_verify_use_backup_codes' => 'Verificar usando un código de respaldo',
+ 'mfa_verify_backup_code' => 'Códigos de Respaldo',
+ 'mfa_verify_backup_code_desc' => 'Introduzca uno de sus códigos de respaldo restantes a continuación:',
+ 'mfa_verify_backup_code_enter_here' => 'Introduce el código de respaldo aquí',
+ 'mfa_verify_totp_desc' => 'Introduzca el código, generado con tu aplicación móvil, a continuación:',
+ 'mfa_setup_login_notification' => 'Método de dos factores configurado. Por favor, inicia sesión de nuevo utilizando el método configurado.',
];
\ No newline at end of file
'reset' => 'Restablecer',
'remove' => 'Remover',
'add' => 'Agregar',
+ 'configure' => 'Configurar',
'fullscreen' => 'Pantalla completa',
'favourite' => 'Favoritos',
'unfavourite' => 'Eliminar de favoritos',
'no_activity' => 'Ninguna actividad para mostrar',
'no_items' => 'No hay elementos disponibles',
'back_to_top' => 'Volver arriba',
+ 'skip_to_main_content' => 'Ir al contenido principal',
'toggle_details' => 'Alternar detalles',
'toggle_thumbnails' => 'Alternar miniaturas',
'details' => 'Detalles',
'export_html' => 'Archivo web contenido',
'export_pdf' => 'Archivo PDF',
'export_text' => 'Archivo de texto plano',
+ 'export_md' => 'Archivo Markdown',
// Permissions and restrictions
'permissions' => 'Permisos',
'shelves_permissions' => 'Permisos del Estante',
'shelves_permissions_updated' => 'Permisos del Estante actualizados',
'shelves_permissions_active' => 'Permisos Activos del Estante',
+ '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' => 'Copiar Permisos a los Libros',
'shelves_copy_permissions' => 'Copiar Permisos',
'shelves_copy_permissions_explain' => 'Esta acción aplicará los permisos de este estante a todos los libros contenidos en él. Antes de activarlos, asegúrese que los cambios a los permisos de este estante estén guardados.',
'recycle_bin' => 'Papelera de Reciclaje',
'recycle_bin_desc' => 'Aquí puede restaurar elementos que hayan sido eliminados o elegir eliminarlos permanentemente del sistema. Esta lista no está filtrada a diferencia de las listas de actividad similares en el sistema donde se aplican los filtros de permisos.',
'recycle_bin_deleted_item' => 'Elemento Eliminado',
+ 'recycle_bin_deleted_parent' => 'Superior',
'recycle_bin_deleted_by' => 'Eliminado por',
'recycle_bin_deleted_at' => 'Fecha de eliminación',
'recycle_bin_permanently_delete' => 'Eliminar permanentemente',
'recycle_bin_restore_list' => 'Elementos a restaurar',
'recycle_bin_restore_confirm' => 'Esta acción restaurará el elemento eliminado, incluyendo cualquier elemento secundario, a su ubicación original. Si la ubicación original ha sido eliminada, y ahora está en la papelera de reciclaje, el elemento padre también tendrá que ser restaurado.',
'recycle_bin_restore_deleted_parent' => 'El padre de este elemento también ha sido eliminado. Estos permanecerán eliminados hasta que el padre también sea restaurado.',
+ 'recycle_bin_restore_parent' => 'Restaurar Superior',
'recycle_bin_destroy_notification' => 'Eliminados :count elementos de la papelera de reciclaje.',
'recycle_bin_restore_notification' => 'Restaurados :count elementos desde la papelera de reciclaje.',
'audit_table_user' => 'Usuario',
'audit_table_event' => 'Evento',
'audit_table_related' => 'Elemento o detalle relacionados',
+ 'audit_table_ip' => 'IP Address',
'audit_table_date' => 'Fecha de la Actividad',
'audit_date_from' => 'Inicio del Rango de Fecha',
'audit_date_to' => 'Final del Rango de Fecha',
'role_details' => 'Detalles de rol',
'role_name' => 'Nombre de rol',
'role_desc' => 'Descripción corta de rol',
+ 'role_mfa_enforced' => 'Requiere Autenticación en Dos Pasos',
'role_external_auth_id' => 'IDs de Autenticación Externa',
'role_system' => 'Permisos de sistema',
'role_manage_users' => 'Gestionar usuarios',
'role_manage_page_templates' => 'Gestionar las plantillas de páginas',
'role_access_api' => 'API de sistema de acceso',
'role_manage_settings' => 'Gestionar ajustes de activos',
+ 'role_export_content' => 'Exportar contenido',
'role_asset' => 'Permisos de activos',
'roles_system_warning' => 'Tenga en cuenta que el acceso a cualquiera de los tres permisos anteriores puede permitir a un usuario modificar sus propios privilegios o los privilegios de otros usuarios en el sistema. Asignar roles con estos permisos sólo a usuarios de comfianza.',
'role_asset_desc' => 'Estos permisos controlan el acceso por defecto a los activos del sistema. Permisos definidos en Libros, Capítulos y Páginas ignorarán estos permisos.',
'users_api_tokens_create' => 'Crear token',
'users_api_tokens_expires' => 'Expira',
'users_api_tokens_docs' => 'Documentación API',
+ 'users_mfa' => 'Autenticación en Dos Pasos',
+ 'users_mfa_desc' => 'La autenticación en dos pasos añade una capa de seguridad adicional a tu cuenta.',
+ 'users_mfa_x_methods' => ':count método configurado|:count métodos configurados',
+ 'users_mfa_configure' => 'Configurar Métodos',
// API Tokens
'user_api_token_create' => 'Crear token API',
'it' => 'Italian',
'ja' => '日本語',
'ko' => '한국어',
+ 'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nl' => 'Nederlands',
'nb' => 'Norsk (Bokmål)',
'alpha_dash' => 'El :attribute solo puede contener letras, números y guiones.',
'alpha_num' => 'El :attribute solo puede contener letras y número.',
'array' => 'El :attribute debe de ser un array.',
+ 'backup_codes' => 'El código suministrado no es válido o ya ha sido utilizado.',
'before' => 'El :attribute debe ser una fecha anterior a :date.',
'between' => [
'numeric' => 'El :attribute debe estar entre :min y :max.',
],
'string' => 'El atributo :attribute debe ser una cadena.',
'timezone' => 'El atributo :attribute debe ser una zona válida.',
+ 'totp' => 'El código suministrado no es válido o ya ha expirado.',
'unique' => 'El atributo :attribute ya ha sido tomado.',
'url' => 'El atributo :attribute tiene un formato inválido.',
'uploaded' => 'El archivo no se pudo subir. Puede ser que el servidor no acepte archivos de este tamaño.',
return [
// Pages
- 'page_create' => 'created page',
- 'page_create_notification' => 'Page Successfully Created',
- 'page_update' => 'updated page',
- 'page_update_notification' => 'Page Successfully Updated',
- 'page_delete' => 'deleted page',
- 'page_delete_notification' => 'Page Successfully Deleted',
- 'page_restore' => 'restored page',
- 'page_restore_notification' => 'Page Successfully Restored',
- 'page_move' => 'moved page',
+ 'page_create' => 'صفحه ایجاد شده',
+ 'page_create_notification' => 'صفحه با موفقیت ایجاد شد',
+ 'page_update' => 'صفحه بروز شده',
+ 'page_update_notification' => 'صفحه با موفقیت به روزرسانی شد',
+ 'page_delete' => 'حذف صفحه',
+ 'page_delete_notification' => 'صفحه با موفقیت حذف شد',
+ 'page_restore' => 'بازیابی صفحه',
+ 'page_restore_notification' => 'صفحه با موفقیت بازیابی شد',
+ 'page_move' => 'انتقال صفحه',
// Chapters
- 'chapter_create' => 'created chapter',
- 'chapter_create_notification' => 'Chapter Successfully Created',
- 'chapter_update' => 'updated chapter',
- 'chapter_update_notification' => 'Chapter Successfully Updated',
- 'chapter_delete' => 'deleted chapter',
- 'chapter_delete_notification' => 'Chapter Successfully Deleted',
- 'chapter_move' => 'moved chapter',
+ 'chapter_create' => 'ایجاد فصل',
+ 'chapter_create_notification' => 'فصل با موفقیت ایجاد شد',
+ 'chapter_update' => 'به روزرسانی فصل',
+ 'chapter_update_notification' => 'فصل با موفقیت به روزرسانی شد',
+ 'chapter_delete' => 'حذف فصل',
+ 'chapter_delete_notification' => 'فصل با موفقیت حذف شد',
+ 'chapter_move' => 'انتقال فصل',
// Books
- 'book_create' => 'created book',
- 'book_create_notification' => 'Book Successfully Created',
- 'book_update' => 'updated book',
- 'book_update_notification' => 'Book Successfully Updated',
- 'book_delete' => 'deleted book',
- 'book_delete_notification' => 'Book Successfully Deleted',
- 'book_sort' => 'sorted book',
- 'book_sort_notification' => 'Book Successfully Re-sorted',
+ 'book_create' => 'ایجاد کتاب',
+ 'book_create_notification' => 'کتاب با موفقیت ایجاد شد',
+ 'book_update' => 'به روزرسانی کتاب',
+ 'book_update_notification' => 'کتاب با موفقیت به روزرسانی شد',
+ 'book_delete' => 'حذف کتاب',
+ 'book_delete_notification' => 'کتاب با موفقیت حذف شد',
+ 'book_sort' => 'مرتب سازی کتاب',
+ 'book_sort_notification' => 'کتاب با موفقیت مرتب سازی شد',
// Bookshelves
- 'bookshelf_create' => 'created Bookshelf',
- 'bookshelf_create_notification' => 'Bookshelf Successfully Created',
- 'bookshelf_update' => 'updated bookshelf',
- 'bookshelf_update_notification' => 'Bookshelf Successfully Updated',
- 'bookshelf_delete' => 'deleted bookshelf',
- 'bookshelf_delete_notification' => 'Bookshelf Successfully Deleted',
+ 'bookshelf_create' => 'ایجاد قفسه کتاب',
+ 'bookshelf_create_notification' => 'قفسه کتاب با موفقیت ایجاد شد',
+ 'bookshelf_update' => 'به روزرسانی قفسه کتاب',
+ 'bookshelf_update_notification' => 'قفسه کتاب با موفقیت به روزرسانی شد',
+ 'bookshelf_delete' => 'حذف قفسه کتاب',
+ 'bookshelf_delete_notification' => 'قفسه کتاب با موفقیت حذف شد',
// Favourites
- 'favourite_add_notification' => '":name" has been added to your favourites',
- 'favourite_remove_notification' => '":name" has been removed from your favourites',
+ 'favourite_add_notification' => '":name" به علاقه مندی های شما اضافه شد',
+ 'favourite_remove_notification' => '":name" از علاقه مندی های شما حذف شد',
+
+ // MFA
+ 'mfa_setup_method_notification' => 'Multi-factor method successfully configured',
+ 'mfa_remove_method_notification' => 'Multi-factor method successfully removed',
// Other
- 'commented_on' => 'commented on',
- 'permissions_update' => 'updated permissions',
+ 'commented_on' => 'ثبت دیدگاه',
+ 'permissions_update' => 'به روزرسانی مجوزها',
];
*/
return [
- 'failed' => 'These credentials do not match our records.',
- 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
+ 'failed' => 'مشخصات وارد شده با اطلاعات ما سازگار نیست.',
+ 'throttle' => 'دفعات تلاش شما برای ورود بیش از حد مجاز است. لطفا پس از :seconds ثانیه مجددا تلاش فرمایید.',
// Login & Register
- 'sign_up' => 'Sign up',
- 'log_in' => 'Log in',
- 'log_in_with' => 'Login with :socialDriver',
- 'sign_up_with' => 'Sign up with :socialDriver',
- 'logout' => 'Logout',
+ 'sign_up' => 'ثبت نام',
+ 'log_in' => 'ورود',
+ 'log_in_with' => 'ورود با :socialDriver',
+ 'sign_up_with' => 'ثبت نام با :socialDriver',
+ 'logout' => 'خروج',
- 'name' => 'Name',
- 'username' => 'Username',
- 'email' => 'Email',
- 'password' => 'Password',
- 'password_confirm' => 'Confirm Password',
- 'password_hint' => 'Must be over 7 characters',
- 'forgot_password' => 'Forgot Password?',
- 'remember_me' => 'Remember Me',
- 'ldap_email_hint' => 'Please enter an email to use for this account.',
- 'create_account' => 'Create Account',
- 'already_have_account' => 'Already have an account?',
- 'dont_have_account' => 'Don\'t have an account?',
- 'social_login' => 'Social Login',
- 'social_registration' => 'Social Registration',
- 'social_registration_text' => 'Register and sign in using another service.',
+ 'name' => 'نام',
+ 'username' => 'نام کاربری',
+ 'email' => 'پست الکترونیک',
+ 'password' => 'کلمه عبور',
+ 'password_confirm' => 'تایید کلمه عبور',
+ 'password_hint' => 'باید بیش از 7 کاراکتر باشد',
+ 'forgot_password' => 'کلمه عبور خود را فراموش کرده اید؟',
+ 'remember_me' => 'مرا به خاطر بسپار',
+ 'ldap_email_hint' => 'لطفا برای استفاده از این حساب کاربری پست الکترونیک وارد نمایید.',
+ 'create_account' => 'ایجاد حساب کاربری',
+ 'already_have_account' => 'قبلا ثبت نام نموده اید؟',
+ 'dont_have_account' => 'حساب کاربری ندارید؟',
+ 'social_login' => 'ورود از طریق شبکه اجتماعی',
+ 'social_registration' => 'ثبت نام از طریق شبکه اجتماعی',
+ 'social_registration_text' => 'با استفاده از سرویس دیگری ثبت نام نموده و وارد سیستم شوید.',
- 'register_thanks' => 'Thanks for registering!',
- 'register_confirm' => 'Please check your email and click the confirmation button to access :appName.',
- 'registrations_disabled' => 'Registrations are currently disabled',
- 'registration_email_domain_invalid' => 'That email domain does not have access to this application',
- 'register_success' => 'Thanks for signing up! You are now registered and signed in.',
+ 'register_thanks' => 'از ثبت نام شما متشکریم!',
+ 'register_confirm' => 'لطفا پست الکترونیک خود را بررسی نموده و برای دسترسی به:appName دکمه تایید را کلیک نمایید.',
+ 'registrations_disabled' => 'ثبت نام در حال حاضر غیر فعال است',
+ 'registration_email_domain_invalid' => 'دامنه پست الکترونیک به این برنامه دسترسی ندارد',
+ 'register_success' => 'از ثبت نام شما سپاسگزاریم! شما اکنون ثبت نام کرده و وارد سیستم شده اید.',
// Password Reset
- 'reset_password' => 'Reset Password',
- 'reset_password_send_instructions' => 'Enter your email below and you will be sent an email with a password reset link.',
- 'reset_password_send_button' => 'Send Reset Link',
- 'reset_password_sent' => 'A password reset link will be sent to :email if that email address is found in the system.',
- 'reset_password_success' => 'Your password has been successfully reset.',
- 'email_reset_subject' => 'Reset your :appName password',
- 'email_reset_text' => 'You are receiving this email because we received a password reset request for your account.',
- 'email_reset_not_requested' => 'If you did not request a password reset, no further action is required.',
+ 'reset_password' => 'بازنشانی کلمه عبور',
+ 'reset_password_send_instructions' => 'پست الکترونیک خود را در کادر زیر وارد نموده تا یک پیام حاوی لینک بازنشانی کلمه عبور دریافت نمایید.',
+ 'reset_password_send_button' => 'ارسال لینک بازنشانی',
+ 'reset_password_sent' => 'در صورت موجود بودن پست الکترونیک، یک لینک بازنشانی کلمه عبور برای شما ارسال خواهد شد.',
+ 'reset_password_success' => 'کلمه عبور شما با موفقیت بازنشانی شد.',
+ 'email_reset_subject' => 'بازنشانی کلمه عبور :appName',
+ 'email_reset_text' => 'شما این پیام را به علت درخواست بازنشانی کلمه عبور دریافت می نمایید.',
+ 'email_reset_not_requested' => 'در صورتی که درخواست بازنشانی کلمه عبور از سمت شما نمی باشد، نیاز به انجام هیچ فعالیتی ندارید.',
// Email Confirmation
- 'email_confirm_subject' => 'Confirm your email on :appName',
- 'email_confirm_greeting' => 'Thanks for joining :appName!',
- 'email_confirm_text' => 'Please confirm your email address by clicking the button below:',
- 'email_confirm_action' => 'Confirm Email',
- 'email_confirm_send_error' => 'Email confirmation required but the system could not send the email. Contact the admin to ensure email is set up correctly.',
- 'email_confirm_success' => 'Your email has been confirmed!',
- 'email_confirm_resent' => 'Confirmation email resent, Please check your inbox.',
+ 'email_confirm_subject' => 'پست الکترونیک خود را در:appName تایید نمایید',
+ 'email_confirm_greeting' => 'برای پیوستن به :appName متشکریم!',
+ 'email_confirm_text' => 'لطفا با کلیک بر روی دکمه زیر پست الکترونیک خود را تایید نمایید:',
+ 'email_confirm_action' => 'تایید پست الکترونیک',
+ 'email_confirm_send_error' => 'تایید پست الکترونیک الزامی می باشد، اما سیستم قادر به ارسال پیام نمی باشد.',
+ 'email_confirm_success' => 'پست الکترونیک شما تایید گردیده است!',
+ 'email_confirm_resent' => 'پیام تایید پست الکترونیک مجدد ارسال گردید، لطفا صندوق ورودی خود را بررسی نمایید.',
- 'email_not_confirmed' => 'Email Address Not Confirmed',
- 'email_not_confirmed_text' => 'Your email address has not yet been confirmed.',
- 'email_not_confirmed_click_link' => 'Please click the link in the email that was sent shortly after you registered.',
- 'email_not_confirmed_resend' => 'If you cannot find the email you can re-send the confirmation email by submitting the form below.',
- 'email_not_confirmed_resend_button' => 'Resend Confirmation Email',
+ 'email_not_confirmed' => 'پست الکترونیک تایید نشده است',
+ 'email_not_confirmed_text' => 'پست الکترونیک شما هنوز تایید نشده است.',
+ 'email_not_confirmed_click_link' => 'لطفا بر روی لینک موجود در پیامی که بلافاصله پس از ثبت نام ارسال شده است کلیک نمایید.',
+ 'email_not_confirmed_resend' => 'در صورتی که نمی توانید پیام را پیدا کنید، می توانید با ارسال فرم زیر، پیام تایید را مجدد دریافت نمایید.',
+ 'email_not_confirmed_resend_button' => 'ارسال مجدد تایید پست الکترونیک',
// User Invite
- 'user_invite_email_subject' => 'You have been invited to join :appName!',
- 'user_invite_email_greeting' => 'An account has been created for you on :appName.',
- 'user_invite_email_text' => 'Click the button below to set an account password and gain access:',
- 'user_invite_email_action' => 'Set Account Password',
- 'user_invite_page_welcome' => 'Welcome to :appName!',
- 'user_invite_page_text' => 'To finalise your account and gain access you need to set a password which will be used to log-in to :appName on future visits.',
- 'user_invite_page_confirm_button' => 'Confirm Password',
- 'user_invite_success' => 'Password set, you now have access to :appName!'
+ 'user_invite_email_subject' => 'از شما برای پیوستن به :appName دعوت شده است!',
+ 'user_invite_email_greeting' => 'حساب کاربری برای شما در :appName ایجاد شده است.',
+ 'user_invite_email_text' => 'برای تنظیم کلمه عبور و دسترسی به حساب کاربری بر روی دکمه زیر کلیک نمایید:',
+ 'user_invite_email_action' => 'تنظیم کلمه عبور حسابکاربری',
+ 'user_invite_page_welcome' => 'به :appName خوش آمدید!',
+ 'user_invite_page_text' => 'برای نهایی کردن حساب کاربری خود در :appName و دسترسی به آن، می بایست یک کلمه عبور تنظیم نمایید.',
+ 'user_invite_page_confirm_button' => 'تایید کلمه عبور',
+ 'user_invite_success' => 'کلمه عبور تنظیم شده است، شما اکنون به :appName دسترسی دارید!',
+
+ // 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' => '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.',
];
\ No newline at end of file
return [
// Buttons
- 'cancel' => 'Cancel',
- 'confirm' => 'Confirm',
- 'back' => 'Back',
- 'save' => 'Save',
- 'continue' => 'Continue',
- 'select' => 'Select',
- 'toggle_all' => 'Toggle All',
- 'more' => 'More',
+ 'cancel' => 'لغو',
+ 'confirm' => 'تایید',
+ 'back' => 'بازگشت',
+ 'save' => 'ذخیره',
+ 'continue' => 'ادامه',
+ 'select' => 'انتخاب',
+ 'toggle_all' => 'معکوس کردن همه',
+ 'more' => 'بیشتر',
// Form Labels
- 'name' => 'Name',
- 'description' => 'Description',
- 'role' => 'Role',
- 'cover_image' => 'Cover image',
- 'cover_image_description' => 'This image should be approx 440x250px.',
+ 'name' => 'نام',
+ 'description' => 'توضیحات',
+ 'role' => 'نقش',
+ 'cover_image' => 'تصویر روی جلد',
+ 'cover_image_description' => 'سایز تصویر باید 440x250 باشد.',
// Actions
- 'actions' => 'Actions',
- 'view' => 'View',
- 'view_all' => 'View All',
- 'create' => 'Create',
- 'update' => 'Update',
- 'edit' => 'Edit',
- 'sort' => 'Sort',
- 'move' => 'Move',
- 'copy' => 'Copy',
- 'reply' => 'Reply',
- 'delete' => 'Delete',
- 'delete_confirm' => 'Confirm Deletion',
- 'search' => 'Search',
- 'search_clear' => 'Clear Search',
- 'reset' => 'Reset',
- 'remove' => 'Remove',
- 'add' => 'Add',
- 'fullscreen' => 'Fullscreen',
- 'favourite' => 'Favourite',
+ 'actions' => 'عملیات',
+ 'view' => 'نمایش',
+ 'view_all' => 'نمایش همه',
+ 'create' => 'ایجاد',
+ 'update' => 'بهروز رسانی',
+ 'edit' => 'ويرايش',
+ 'sort' => 'مرتب سازی',
+ 'move' => 'جابجایی',
+ 'copy' => 'کپی',
+ 'reply' => 'پاسخ',
+ 'delete' => 'حذف',
+ 'delete_confirm' => 'تأیید حذف',
+ 'search' => 'جستجو',
+ 'search_clear' => 'پاک کردن جستجو',
+ 'reset' => 'بازنشانی',
+ 'remove' => 'حذف',
+ 'add' => 'ﺍﻓﺰﻭﺩﻥ',
+ 'configure' => 'Configure',
+ 'fullscreen' => 'تمام صفحه',
+ 'favourite' => 'علاقهمندی',
'unfavourite' => 'Unfavourite',
- 'next' => 'Next',
- 'previous' => 'Previous',
+ 'next' => 'بعدی',
+ 'previous' => 'قبلى',
// Sort Options
- 'sort_options' => 'Sort Options',
- 'sort_direction_toggle' => 'Sort Direction Toggle',
- 'sort_ascending' => 'Sort Ascending',
- 'sort_descending' => 'Sort Descending',
- 'sort_name' => 'Name',
- 'sort_default' => 'Default',
- 'sort_created_at' => 'Created Date',
- 'sort_updated_at' => 'Updated Date',
+ 'sort_options' => 'گزینههای مرتب سازی',
+ 'sort_direction_toggle' => 'معکوس کردن جهت مرتب سازی',
+ 'sort_ascending' => 'مرتبسازی صعودی',
+ 'sort_descending' => 'مرتبسازی نزولی',
+ 'sort_name' => 'نام',
+ 'sort_default' => 'پیشفرض',
+ 'sort_created_at' => 'تاریخ ایجاد',
+ 'sort_updated_at' => 'تاریخ بروزرسانی',
// Misc
- 'deleted_user' => 'Deleted User',
- 'no_activity' => 'No activity to show',
- 'no_items' => 'No items available',
- 'back_to_top' => 'Back to top',
- 'toggle_details' => 'Toggle Details',
- 'toggle_thumbnails' => 'Toggle Thumbnails',
- 'details' => 'Details',
- 'grid_view' => 'Grid View',
- 'list_view' => 'List View',
- 'default' => 'Default',
- 'breadcrumb' => 'Breadcrumb',
+ 'deleted_user' => 'کاربر حذف شده',
+ 'no_activity' => 'بایگانی برای نمایش وجود ندارد',
+ 'no_items' => 'هیچ آیتمی موجود نیست',
+ 'back_to_top' => 'بازگشت به بالا',
+ 'skip_to_main_content' => 'رفتن به محتوای اصلی',
+ 'toggle_details' => 'معکوس کردن اطلاعات',
+ 'toggle_thumbnails' => 'معکوس ریز عکس ها',
+ 'details' => 'جزییات',
+ 'grid_view' => 'نمایش شبکهای',
+ 'list_view' => 'نمای لیست',
+ 'default' => 'پیشفرض',
+ 'breadcrumb' => 'مسیر جاری',
// Header
- 'header_menu_expand' => 'Expand Header Menu',
- 'profile_menu' => 'Profile Menu',
- 'view_profile' => 'View Profile',
- 'edit_profile' => 'Edit Profile',
- 'dark_mode' => 'Dark Mode',
- 'light_mode' => 'Light Mode',
+ 'header_menu_expand' => 'گسترش منو',
+ 'profile_menu' => 'منو پروفایل',
+ 'view_profile' => 'مشاهده پروفایل',
+ 'edit_profile' => 'ویرایش پروفایل',
+ 'dark_mode' => 'حالت تاریک',
+ 'light_mode' => 'حالت روشن',
// Layout tabs
- 'tab_info' => 'Info',
- 'tab_info_label' => 'Tab: Show Secondary Information',
- 'tab_content' => 'Content',
- 'tab_content_label' => 'Tab: Show Primary Content',
+ 'tab_info' => 'اطلاعات',
+ 'tab_info_label' => 'زبانه: نمایش اطلاعات ثانویه',
+ 'tab_content' => 'محتوا',
+ 'tab_content_label' => 'زبانه: نمایش محتوای اصلی',
// Email Content
- 'email_action_help' => 'If you’re having trouble clicking the ":actionText" button, copy and paste the URL below into your web browser:',
- 'email_rights' => 'All rights reserved',
+ 'email_action_help' => 'اگر با دکمه بالا مشکلی دارید ، ادرس وبسایت *URLزیر را در مرورگر وب خود کپی و پیست کنید:',
+ 'email_rights' => 'تمام حقوق محفوظ است',
// Footer Link Options
// Not directly used but available for convenience to users.
- 'privacy_policy' => 'Privacy Policy',
- 'terms_of_service' => 'Terms of Service',
+ 'privacy_policy' => 'سیاست حفظ حریم خصوصی',
+ 'terms_of_service' => 'شرایط خدمات',
];
return [
// Image Manager
- 'image_select' => 'Image Select',
- 'image_all' => 'All',
- 'image_all_title' => 'View all images',
- 'image_book_title' => 'View images uploaded to this book',
- 'image_page_title' => 'View images uploaded to this page',
- 'image_search_hint' => 'Search by image name',
- 'image_uploaded' => 'Uploaded :uploadedDate',
- 'image_load_more' => 'Load More',
- 'image_image_name' => 'Image Name',
- 'image_delete_used' => 'This image is used in the pages below.',
- 'image_delete_confirm_text' => 'Are you sure you want to delete this image?',
- 'image_select_image' => 'Select Image',
- 'image_dropzone' => 'Drop images or click here to upload',
- 'images_deleted' => 'Images Deleted',
- 'image_preview' => 'Image Preview',
- 'image_upload_success' => 'Image uploaded successfully',
- 'image_update_success' => 'Image details successfully updated',
- 'image_delete_success' => 'Image successfully deleted',
- 'image_upload_remove' => 'Remove',
+ 'image_select' => 'انتخاب تصویر',
+ 'image_all' => 'همه',
+ 'image_all_title' => 'نمایش تمام تصاویر',
+ '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' => 'Edit Code',
- 'code_language' => 'Code Language',
- 'code_content' => 'Code Content',
- 'code_session_history' => 'Session History',
- 'code_save' => 'Save Code',
+ 'code_editor' => 'ویرایش کد',
+ 'code_language' => 'زبان کد',
+ 'code_content' => 'محتوی کد',
+ 'code_session_history' => 'تاریخچه جلسات',
+ 'code_save' => 'ذخیره کد',
];
return [
// Shared
- 'recently_created' => 'Recently Created',
- 'recently_created_pages' => 'Recently Created Pages',
- 'recently_updated_pages' => 'Recently Updated Pages',
- 'recently_created_chapters' => 'Recently Created Chapters',
- 'recently_created_books' => 'Recently Created Books',
- 'recently_created_shelves' => 'Recently Created Shelves',
- 'recently_update' => 'Recently Updated',
- 'recently_viewed' => 'Recently Viewed',
- 'recent_activity' => 'Recent Activity',
- 'create_now' => 'Create one now',
- 'revisions' => 'Revisions',
- 'meta_revision' => 'Revision #:revisionCount',
- 'meta_created' => 'Created :timeLength',
- 'meta_created_name' => 'Created :timeLength by :user',
- 'meta_updated' => 'Updated :timeLength',
- 'meta_updated_name' => 'Updated :timeLength by :user',
- 'meta_owned_name' => 'Owned by :user',
- 'entity_select' => 'Entity Select',
- 'images' => 'Images',
- 'my_recent_drafts' => 'My Recent Drafts',
- 'my_recently_viewed' => 'My Recently Viewed',
- 'my_most_viewed_favourites' => 'My Most Viewed Favourites',
- 'my_favourites' => 'My Favourites',
- 'no_pages_viewed' => 'You have not viewed any pages',
- 'no_pages_recently_created' => 'No pages have been recently created',
- 'no_pages_recently_updated' => 'No pages have been recently updated',
- 'export' => 'Export',
- 'export_html' => 'Contained Web File',
- 'export_pdf' => 'PDF File',
- 'export_text' => 'Plain Text File',
+ 'recently_created' => 'اخیرا ایجاد شده',
+ '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 توسط :user',
+ 'meta_updated' => 'به روزرسانی شده :timeLength',
+ 'meta_updated_name' => 'به روزرسانی شده :timeLength توسط :user',
+ 'meta_owned_name' => 'توسط :user ایجاد شدهاست',
+ 'entity_select' => 'انتخاب موجودیت',
+ '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' => 'فایل وب موجود است',
+ 'export_pdf' => 'فایل PDF',
+ 'export_text' => 'پرونده متنی ساده',
+ 'export_md' => 'راهنما مارکدون',
// 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',
+ 'permissions' => 'مجوزها',
+ 'permissions_intro' => 'پس از فعال شدن، این مجوزها نسبت به مجوزهای تعیین شده نقش اولویت دارند.',
+ 'permissions_enable' => 'مجوزهای سفارشی را فعال کنید',
+ 'permissions_save' => 'ذخيره مجوزها',
+ 'permissions_owner' => 'مالک',
// Search
- 'search_results' => 'Search Results',
+ 'search_results' => 'نتایج جستجو',
'search_total_results_found' => ':count result found|:count total results found',
- 'search_clear' => 'Clear Search',
- 'search_no_pages' => 'No pages matched this search',
- 'search_for_term' => 'Search for :term',
- 'search_more' => 'More Results',
- 'search_advanced' => 'Advanced Search',
- 'search_terms' => 'Search Terms',
- 'search_content_type' => 'Content Type',
- 'search_exact_matches' => 'Exact Matches',
- 'search_tags' => 'Tag Searches',
- 'search_options' => 'Options',
- 'search_viewed_by_me' => 'Viewed by me',
- 'search_not_viewed_by_me' => 'Not viewed by me',
+ 'search_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' => 'Permissions set',
'search_created_by_me' => 'Created by me',
'search_updated_by_me' => 'Updated by me',
'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' => 'Copy Permissions to Books',
'shelves_copy_permissions' => 'Copy Permissions',
'shelves_copy_permissions_explain' => 'This will apply the current permission settings of this bookshelf to all books contained within. Before activating, ensure any changes to the permissions of this bookshelf have been saved.',
return [
// Permissions
- 'permission' => 'You do not have permission to access the requested page.',
- 'permissionJson' => 'You do not have permission to perform the requested action.',
+ 'permission' => 'شما مجوز مشاهده صفحه درخواست شده را ندارید.',
+ 'permissionJson' => 'شما مجاز به انجام این عمل نیستید.',
// Auth
- 'error_user_exists_different_creds' => 'A user with the email :email already exists but with different credentials.',
- 'email_already_confirmed' => 'Email has already been confirmed, Try logging in.',
- 'email_confirmation_invalid' => 'This confirmation token is not valid or has already been used, Please try registering again.',
- 'email_confirmation_expired' => 'The confirmation token has expired, A new confirmation email has been sent.',
- 'email_confirmation_awaiting' => 'The email address for the account in use needs to be confirmed',
- 'ldap_fail_anonymous' => 'LDAP access failed using anonymous bind',
- 'ldap_fail_authed' => 'LDAP access failed using given dn & password details',
- 'ldap_extension_not_installed' => 'LDAP PHP extension not installed',
- 'ldap_cannot_connect' => 'Cannot connect to ldap server, Initial connection failed',
- 'saml_already_logged_in' => 'Already logged in',
- 'saml_user_not_registered' => 'The user :name is not registered and automatic registration is disabled',
- 'saml_no_email_address' => 'Could not find an email address, for this user, in the data provided by the external authentication system',
- 'saml_invalid_response_id' => 'The request from the external authentication system is not recognised by a process started by this application. Navigating back after a login could cause this issue.',
- 'saml_fail_authed' => 'Login using :system failed, system did not provide successful authorization',
- 'social_no_action_defined' => 'No action defined',
- 'social_login_bad_response' => "Error received during :socialAccount login: \n:error",
- 'social_account_in_use' => 'This :socialAccount account is already in use, Try logging in via the :socialAccount option.',
- 'social_account_email_in_use' => 'The email :email is already in use. If you already have an account you can connect your :socialAccount account from your profile settings.',
- 'social_account_existing' => 'This :socialAccount is already attached to your profile.',
- 'social_account_already_used_existing' => 'This :socialAccount account is already used by another user.',
- 'social_account_not_used' => 'This :socialAccount account is not linked to any users. Please attach it in your profile settings. ',
- 'social_account_register_instructions' => 'If you do not yet have an account, You can register an account using the :socialAccount option.',
- 'social_driver_not_found' => 'Social driver not found',
- 'social_driver_not_configured' => 'Your :socialAccount social settings are not configured correctly.',
+ 'error_user_exists_different_creds' => 'کاربری با ایمیل :email از قبل وجود دارد اما دارای اطلاعات متفاوتی می باشد.',
+ 'email_already_confirmed' => 'ایمیل قبلا تایید شده است، وارد سیستم شوید.',
+ 'email_confirmation_invalid' => 'این کلمه عبور معتبر نمی باشد و یا قبلا استفاده شده است، لطفا دوباره ثبت نام نمایید.',
+ 'email_confirmation_expired' => 'کلمه عبور منقضی شده است، یک ایمیل تایید جدید ارسال شد.',
+ 'email_confirmation_awaiting' => 'آدرس ایمیل حساب مورد استفاده باید تایید شود',
+ 'ldap_fail_anonymous' => 'دسترسی LDAP با استفاده از صحافی ناشناس انجام نشد',
+ 'ldap_fail_authed' => 'دسترسی به LDAP با استفاده از جزئیات داده شده و رمز عبور انجام نشد',
+ 'ldap_extension_not_installed' => 'افزونه PHP LDAP نصب نشده است',
+ '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 انجام نشد، سیستم مجوز موفقیت آمیز ارائه نکرد',
+ 'social_no_action_defined' => 'عملی تعریف نشده است',
+ 'social_login_bad_response' => "خطای دریافت شده در هنگام ورود به سیستم:\n:error",
+ 'social_account_in_use' => 'این حساب :socialAccount از قبل در حال استفاده است، سعی کنید از طریق گزینه :socialAccount وارد سیستم شوید.',
+ 'social_account_email_in_use' => 'ایمیل :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' => 'This invitation link has expired. You can instead try to reset your account password.',
// System
*/
return [
- 'previous' => '« Previous',
- 'next' => 'Next »',
+ 'previous' => '« قبلی',
+ 'next' => 'بعدی »',
];
*/
return [
- 'password' => 'Passwords must be at least eight characters and match the confirmation.',
- 'user' => "We can't find a user with that e-mail address.",
- 'token' => 'The password reset token is invalid for this email address.',
- 'sent' => 'We have e-mailed your password reset link!',
- 'reset' => 'Your password has been reset!',
+ 'password' => 'گذرواژه باید حداقل هشت حرف و با تایید مطابقت داشته باشد.',
+ 'user' => "ما کاربری با این نشانی ایمیل نداریم.",
+ 'token' => 'مشخصهی بازگردانی رمز عبور معتبر نیست.',
+ 'sent' => 'لینک بازگردانی رمز عبور به ایمیل شما ارسال شد!',
+ 'reset' => 'رمز عبور شما بازگردانی شد!',
];
'recycle_bin' => 'Recycle Bin',
'recycle_bin_desc' => 'Here you can restore items that have been deleted or choose to permanently remove them from the system. This list is unfiltered unlike similar activity lists in the system where permission filters are applied.',
'recycle_bin_deleted_item' => 'Deleted Item',
+ 'recycle_bin_deleted_parent' => 'Parent',
'recycle_bin_deleted_by' => 'Deleted By',
'recycle_bin_deleted_at' => 'Deletion Time',
'recycle_bin_permanently_delete' => 'Permanently Delete',
'recycle_bin_restore_list' => 'Items to be Restored',
'recycle_bin_restore_confirm' => 'This action will restore the deleted item, including any child elements, to their original location. If the original location has since been deleted, and is now in the recycle bin, the parent item will also need to be restored.',
'recycle_bin_restore_deleted_parent' => 'The parent of this item has also been deleted. These will remain deleted until that parent is also restored.',
+ 'recycle_bin_restore_parent' => 'Restore Parent',
'recycle_bin_destroy_notification' => 'Deleted :count total items from the recycle bin.',
'recycle_bin_restore_notification' => 'Restored :count total items from the recycle bin.',
'audit_table_user' => 'User',
'audit_table_event' => 'Event',
'audit_table_related' => 'Related Item or Detail',
+ 'audit_table_ip' => 'IP Address',
'audit_table_date' => 'Activity Date',
'audit_date_from' => 'Date Range From',
'audit_date_to' => 'Date Range To',
'role_details' => 'Role Details',
'role_name' => 'Role Name',
'role_desc' => 'Short Description of Role',
+ 'role_mfa_enforced' => 'Requires Multi-Factor Authentication',
'role_external_auth_id' => 'External Authentication IDs',
'role_system' => 'System Permissions',
'role_manage_users' => 'Manage users',
'role_manage_page_templates' => 'Manage page templates',
'role_access_api' => 'Access system API',
'role_manage_settings' => 'Manage app settings',
+ 'role_export_content' => 'Export content',
'role_asset' => 'Asset Permissions',
'roles_system_warning' => 'Be aware that access to any of the above three permissions can allow a user to alter their own privileges or the privileges of others in the system. Only assign roles with these permissions to trusted users.',
'role_asset_desc' => 'These permissions control default access to the assets within the system. Permissions on Books, Chapters and Pages will override these permissions.',
'users_api_tokens_create' => 'Create Token',
'users_api_tokens_expires' => 'Expires',
'users_api_tokens_docs' => 'API Documentation',
+ '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',
// API Tokens
'user_api_token_create' => 'Create API Token',
'it' => 'Italian',
'ja' => '日本語',
'ko' => '한국어',
+ 'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nl' => 'Nederlands',
'nb' => 'Norsk (Bokmål)',
return [
// Standard laravel validation lines
- 'accepted' => 'The :attribute must be accepted.',
- 'active_url' => 'The :attribute is not a valid URL.',
- 'after' => 'The :attribute must be a date after :date.',
- 'alpha' => 'The :attribute may only contain letters.',
- 'alpha_dash' => 'The :attribute may only contain letters, numbers, dashes and underscores.',
- 'alpha_num' => 'The :attribute may only contain letters and numbers.',
- 'array' => 'The :attribute must be an array.',
- 'before' => 'The :attribute must be a date before :date.',
+ 'accepted' => ':attribute باید پذیرفته شده باشد.',
+ 'active_url' => 'آدرس :attribute معتبر نیست.',
+ 'after' => ':attribute باید تاریخی بعد از :date باشد.',
+ 'alpha' => ':attribute باید فقط حروف الفبا باشد.',
+ 'alpha_dash' => ':attribute باید فقط حروف الفبا، اعداد، خط تیره و زیرخط باشد.',
+ 'alpha_num' => ':attribute باید فقط حروف الفبا و اعداد باشد.',
+ 'array' => ':attribute باید آرایه باشد.',
+ 'backup_codes' => 'The provided code is not valid or has already been used.',
+ 'before' => ':attribute باید تاریخی قبل از :date باشد.',
'between' => [
- 'numeric' => 'The :attribute must be between :min and :max.',
- 'file' => 'The :attribute must be between :min and :max kilobytes.',
- 'string' => 'The :attribute must be between :min and :max characters.',
- 'array' => 'The :attribute must have between :min and :max items.',
+ 'numeric' => ':attribute باید بین :min و :max باشد.',
+ 'file' => ':attribute باید بین :min و :max کیلوبایت باشد.',
+ 'string' => ':attribute باید بین :min و :max کاراکتر باشد.',
+ 'array' => ':attribute باید بین :min و :max آیتم باشد.',
],
- 'boolean' => 'The :attribute field must be true or false.',
- 'confirmed' => 'The :attribute confirmation does not match.',
- 'date' => 'The :attribute is not a valid date.',
- 'date_format' => 'The :attribute does not match the format :format.',
- 'different' => 'The :attribute and :other must be different.',
- 'digits' => 'The :attribute must be :digits digits.',
- 'digits_between' => 'The :attribute must be between :min and :max digits.',
- 'email' => 'The :attribute must be a valid email address.',
- 'ends_with' => 'The :attribute must end with one of the following: :values',
- 'filled' => 'The :attribute field is required.',
+ 'boolean' => 'فیلد :attribute فقط میتواند true و یا false باشد.',
+ 'confirmed' => ':attribute با فیلد تکرار مطابقت ندارد.',
+ 'date' => ':attribute یک تاریخ معتبر نیست.',
+ 'date_format' => ':attribute با الگوی :format مطابقت ندارد.',
+ 'different' => ':attribute و :other باید از یکدیگر متفاوت باشند.',
+ 'digits' => ':attribute باید :digits رقم باشد.',
+ 'digits_between' => ':attribute باید بین :min و :max رقم باشد.',
+ 'email' => ':attribute باید یک ایمیل معتبر باشد.',
+ 'ends_with' => 'فیلد :attribute باید با یکی از مقادیر زیر خاتمه یابد: :values',
+ 'filled' => 'فیلد :attribute باید مقدار داشته باشد.',
'gt' => [
- 'numeric' => 'The :attribute must be greater than :value.',
- 'file' => 'The :attribute must be greater than :value kilobytes.',
- 'string' => 'The :attribute must be greater than :value characters.',
- 'array' => 'The :attribute must have more than :value items.',
+ 'numeric' => ':attribute باید بزرگتر از :value باشد.',
+ 'file' => ':attribute باید بزرگتر از :value کیلوبایت باشد.',
+ 'string' => ':attribute باید بیشتر از :value کاراکتر داشته باشد.',
+ 'array' => ':attribute باید بیشتر از :value آیتم داشته باشد.',
],
'gte' => [
- 'numeric' => 'The :attribute must be greater than or equal :value.',
- 'file' => 'The :attribute must be greater than or equal :value kilobytes.',
- 'string' => 'The :attribute must be greater than or equal :value characters.',
- 'array' => 'The :attribute must have :value items or more.',
+ 'numeric' => ':attribute باید بزرگتر یا مساوی :value باشد.',
+ 'file' => ':attribute باید بزرگتر یا مساوی :value کیلوبایت باشد.',
+ 'string' => ':attribute باید بیشتر یا مساوی :value کاراکتر داشته باشد.',
+ 'array' => ':attribute باید بیشتر یا مساوی :value آیتم داشته باشد.',
],
- 'exists' => 'The selected :attribute is invalid.',
- 'image' => 'The :attribute must be an image.',
- 'image_extension' => 'The :attribute must have a valid & supported image extension.',
- 'in' => 'The selected :attribute is invalid.',
- 'integer' => 'The :attribute must be an integer.',
- 'ip' => 'The :attribute must be a valid IP address.',
- 'ipv4' => 'The :attribute must be a valid IPv4 address.',
- 'ipv6' => 'The :attribute must be a valid IPv6 address.',
- 'json' => 'The :attribute must be a valid JSON string.',
+ 'exists' => ':attribute انتخاب شده، معتبر نیست.',
+ 'image' => ':attribute باید یک تصویر معتبر باشد.',
+ 'image_extension' => ':attribute باید یک تصویر با فرمت معتبر باشد.',
+ 'in' => ':attribute انتخاب شده، معتبر نیست.',
+ 'integer' => ':attribute باید عدد صحیح باشد.',
+ 'ip' => ':attribute باید آدرس IP معتبر باشد.',
+ 'ipv4' => ':attribute باید یک آدرس معتبر از نوع IPv4 باشد.',
+ 'ipv6' => ':attribute باید یک آدرس معتبر از نوع IPv6 باشد.',
+ 'json' => 'فیلد :attribute باید یک رشته از نوع JSON باشد.',
'lt' => [
- 'numeric' => 'The :attribute must be less than :value.',
- 'file' => 'The :attribute must be less than :value kilobytes.',
- 'string' => 'The :attribute must be less than :value characters.',
- 'array' => 'The :attribute must have less than :value items.',
+ 'numeric' => ':attribute باید کوچکتر از :value باشد.',
+ 'file' => ':attribute باید کوچکتر از :value کیلوبایت باشد.',
+ 'string' => ':attribute باید کمتر از :value کاراکتر داشته باشد.',
+ 'array' => ':attribute باید کمتر از :value آیتم داشته باشد.',
],
'lte' => [
- 'numeric' => 'The :attribute must be less than or equal :value.',
- 'file' => 'The :attribute must be less than or equal :value kilobytes.',
- 'string' => 'The :attribute must be less than or equal :value characters.',
- 'array' => 'The :attribute must not have more than :value items.',
+ 'numeric' => ':attribute باید کوچکتر یا مساوی :value باشد.',
+ 'file' => ':attribute باید کوچکتر یا مساوی :value کیلوبایت باشد.',
+ 'string' => ':attribute باید کمتر یا مساوی :value کاراکتر داشته باشد.',
+ 'array' => ':attribute باید کمتر یا مساوی :value آیتم داشته باشد.',
],
'max' => [
- 'numeric' => 'The :attribute may not be greater than :max.',
- 'file' => 'The :attribute may not be greater than :max kilobytes.',
- 'string' => 'The :attribute may not be greater than :max characters.',
- 'array' => 'The :attribute may not have more than :max items.',
+ 'numeric' => ':attribute نباید بزرگتر از :max باشد.',
+ 'file' => ':attribute نباید بزرگتر از :max کیلوبایت باشد.',
+ 'string' => ':attribute نباید بیشتر از :max کاراکتر داشته باشد.',
+ 'array' => ':attribute نباید بیشتر از :max آیتم داشته باشد.',
],
- 'mimes' => 'The :attribute must be a file of type: :values.',
+ 'mimes' => 'فرمتهای معتبر فایل عبارتند از: :values.',
'min' => [
- 'numeric' => 'The :attribute must be at least :min.',
- 'file' => 'The :attribute must be at least :min kilobytes.',
- 'string' => 'The :attribute must be at least :min characters.',
- 'array' => 'The :attribute must have at least :min items.',
+ 'numeric' => ':attribute نباید کوچکتر از :min باشد.',
+ 'file' => ':attribute نباید کوچکتر از :min کیلوبایت باشد.',
+ 'string' => ':attribute نباید کمتر از :min کاراکتر داشته باشد.',
+ 'array' => ':attribute نباید کمتر از :min آیتم داشته باشد.',
],
- 'not_in' => 'The selected :attribute is invalid.',
- 'not_regex' => 'The :attribute format is invalid.',
- 'numeric' => 'The :attribute must be a number.',
- 'regex' => 'The :attribute format is invalid.',
- 'required' => 'The :attribute field is required.',
- 'required_if' => 'The :attribute field is required when :other is :value.',
- 'required_with' => 'The :attribute field is required when :values is present.',
- 'required_with_all' => 'The :attribute field is required when :values is present.',
- 'required_without' => 'The :attribute field is required when :values is not present.',
- 'required_without_all' => 'The :attribute field is required when none of :values are present.',
- 'same' => 'The :attribute and :other must match.',
- 'safe_url' => 'The provided link may not be safe.',
+ 'not_in' => ':attribute انتخاب شده، معتبر نیست.',
+ 'not_regex' => 'فرمت :attribute معتبر نیست.',
+ 'numeric' => ':attribute باید عدد یا رشتهای از اعداد باشد.',
+ 'regex' => 'فرمت :attribute معتبر نیست.',
+ 'required' => 'فیلد :attribute الزامی است.',
+ 'required_if' => 'هنگامی که :other برابر با :value است، فیلد :attribute الزامی است.',
+ 'required_with' => 'در صورت وجود فیلد :values، فیلد :attribute نیز الزامی است.',
+ 'required_with_all' => 'در صورت وجود فیلدهای :values، فیلد :attribute نیز الزامی است.',
+ 'required_without' => 'در صورت عدم وجود فیلد :values، فیلد :attribute الزامی است.',
+ 'required_without_all' => 'در صورت عدم وجود هر یک از فیلدهای :values، فیلد :attribute الزامی است.',
+ 'same' => ':attribute و :other باید همانند هم باشند.',
+ 'safe_url' => ':attribute معتبر نمیباشد.',
'size' => [
- 'numeric' => 'The :attribute must be :size.',
- 'file' => 'The :attribute must be :size kilobytes.',
- 'string' => 'The :attribute must be :size characters.',
- 'array' => 'The :attribute must contain :size items.',
+ 'numeric' => ':attribute باید برابر با :size باشد.',
+ 'file' => ':attribute باید برابر با :size کیلوبایت باشد.',
+ 'string' => ':attribute باید برابر با :size کاراکتر باشد.',
+ 'array' => ':attribute باید شامل :size آیتم باشد.',
],
- 'string' => 'The :attribute must be a string.',
- 'timezone' => 'The :attribute must be a valid zone.',
- 'unique' => 'The :attribute has already been taken.',
- 'url' => 'The :attribute format is invalid.',
- 'uploaded' => 'The file could not be uploaded. The server may not accept files of this size.',
+ 'string' => 'فیلد :attribute باید متن باشد.',
+ 'timezone' => 'فیلد :attribute باید یک منطقه زمانی معتبر باشد.',
+ 'totp' => 'The provided code is not valid or has expired.',
+ 'unique' => ':attribute قبلا انتخاب شده است.',
+ 'url' => ':attribute معتبر نمیباشد.',
+ 'uploaded' => 'بارگذاری فایل :attribute موفقیت آمیز نبود.',
// Custom validation lines
'custom' => [
'password-confirm' => [
- 'required_with' => 'Password confirmation required',
+ 'required_with' => 'تایید کلمه عبور اجباری می باشد',
],
],
'chapter_move' => 'a déplacé le chapitre',
// Books
- 'book_create' => 'a créé le livre',
+ 'book_create' => 'a créé un livre',
'book_create_notification' => 'Livre créé avec succès',
'book_update' => 'a modifié le livre',
'book_update_notification' => 'Livre modifié avec succès',
- 'book_delete' => 'a supprimé le livre',
+ 'book_delete' => 'a supprimé un livre',
'book_delete_notification' => 'Livre supprimé avec succès',
'book_sort' => 'a réordonné le livre',
'book_sort_notification' => 'Livre réordonné avec succès',
'favourite_add_notification' => '":name" a été ajouté dans vos favoris',
'favourite_remove_notification' => '":name" a été supprimé de vos favoris',
+ // MFA
+ 'mfa_setup_method_notification' => 'Méthode multi-facteurs configurée avec succès',
+ 'mfa_remove_method_notification' => 'Méthode multi-facteurs supprimée avec succès',
+
// Other
'commented_on' => 'a commenté',
- 'permissions_update' => 'mettre à jour les autorisations',
+ 'permissions_update' => 'a mis à jour les autorisations sur',
];
'email_confirm_action' => 'Confirmez votre adresse e-mail',
'email_confirm_send_error' => 'La confirmation par e-mail est requise mais le système n\'a pas pu envoyer l\'e-mail. Contactez l\'administrateur système.',
'email_confirm_success' => 'Votre adresse e-mail a été confirmée !',
- 'email_confirm_resent' => 'L\'e-mail de confirmation a été ré-envoyé. Vérifiez votre boîte de récéption.',
+ 'email_confirm_resent' => 'L\'e-mail de confirmation a été ré-envoyé. Vérifiez votre boîte de réception.',
'email_not_confirmed' => 'Adresse e-mail non confirmée',
'email_not_confirmed_text' => 'Votre adresse e-mail n\'a pas été confirmée.',
'email_not_confirmed_click_link' => 'Merci de cliquer sur le lien dans l\'e-mail qui vous a été envoyé après l\'enregistrement.',
'email_not_confirmed_resend' => 'Si vous ne retrouvez plus l\'e-mail, vous pouvez renvoyer un e-mail de confirmation en utilisant le formulaire ci-dessous.',
- 'email_not_confirmed_resend_button' => 'Renvoyez l\'e-mail de confirmation',
+ 'email_not_confirmed_resend_button' => 'Renvoyer l\'e-mail de confirmation',
// User Invite
'user_invite_email_subject' => 'Vous avez été invité(e) à rejoindre :appName !',
'user_invite_page_welcome' => 'Bienvenue dans :appName !',
'user_invite_page_text' => 'Pour finaliser votre compte et recevoir l\'accès, vous devez renseigner le mot de passe qui sera utilisé pour la connexion à :appName les prochaines fois.',
'user_invite_page_confirm_button' => 'Confirmez le mot de passe',
- 'user_invite_success' => 'Mot de passe renseigné, vous avez maintenant accès à :appName !'
+ 'user_invite_success' => 'Mot de passe renseigné, vous avez maintenant accès à :appName !',
+
+ // Multi-factor Authentication
+ 'mfa_setup' => 'Authentification multi-facteurs',
+ 'mfa_setup_desc' => 'Configurer l\'authentification multi-facteurs ajoute une couche supplémentaire de sécurité à votre compte utilisateur.',
+ 'mfa_setup_configured' => 'Déjà configuré',
+ 'mfa_setup_reconfigure' => 'Reconfigurer',
+ 'mfa_setup_remove_confirmation' => 'Êtes-vous sûr de vouloir supprimer cette méthode d\'authentification multi-facteurs ?',
+ 'mfa_setup_action' => 'Configuration',
+ 'mfa_backup_codes_usage_limit_warning' => 'Il vous reste moins de 5 codes de secours, veuillez générer et stocker un nouveau jeu de codes afin d\'éviter tout verrouillage de votre compte.',
+ 'mfa_option_totp_title' => 'Application mobile',
+ 'mfa_option_totp_desc' => 'Pour utiliser l\'authentification multi-facteurs, vous aurez besoin d\'une application mobile qui supporte TOTP comme Google Authenticator, Authy ou Microsoft Authenticator.',
+ 'mfa_option_backup_codes_title' => 'Codes de secours',
+ 'mfa_option_backup_codes_desc' => 'Stockez en toute sécurité un jeu de codes de secours que vous pourrez utiliser pour vérifier votre identité.',
+ 'mfa_gen_confirm_and_enable' => 'Confirmer et activer',
+ 'mfa_gen_backup_codes_title' => 'Configuration des codes de secours',
+ 'mfa_gen_backup_codes_desc' => 'Stockez la liste des codes ci-dessous dans un endroit sûr. Lorsque vous accédez au système, vous pourrez utiliser l\'un des codes comme un deuxième mécanisme d\'authentification.',
+ 'mfa_gen_backup_codes_download' => 'Télécharger les codes',
+ 'mfa_gen_backup_codes_usage_warning' => 'Chaque code ne peut être utilisé qu\'une seule fois',
+ 'mfa_gen_totp_title' => 'Configuration de l\'application mobile',
+ 'mfa_gen_totp_desc' => 'Pour utiliser l\'authentification multi-facteurs, vous aurez besoin d\'une application mobile qui supporte TOTP comme Google Authenticator, Authy ou Microsoft Authenticator.',
+ 'mfa_gen_totp_scan' => 'Scannez le QR code ci-dessous avec votre application d\'authentification préférée pour débuter.',
+ 'mfa_gen_totp_verify_setup' => 'Vérifier la configuration',
+ 'mfa_gen_totp_verify_setup_desc' => 'Vérifiez que tout fonctionne en utilisant un code généré par votre application d\'authentification, dans la zone ci-dessous :',
+ 'mfa_gen_totp_provide_code_here' => 'Fournissez le code généré par votre application ici',
+ 'mfa_verify_access' => 'Vérifier l\'accès',
+ 'mfa_verify_access_desc' => 'Votre compte d\'utilisateur vous demande de confirmer votre identité par un niveau supplémentaire de vérification avant que vous n\'ayez accès. Vérifiez-la en utilisant l\'une de vos méthodes configurées pour continuer.',
+ 'mfa_verify_no_methods' => 'Aucune méthode configurée',
+ 'mfa_verify_no_methods_desc' => 'Aucune méthode d\'authentification multi-facteurs n\'a pu être trouvée pour votre compte. Vous devez configurer au moins une méthode avant d\'obtenir l\'accès.',
+ 'mfa_verify_use_totp' => 'Vérifier à l\'aide d\'une application mobile',
+ 'mfa_verify_use_backup_codes' => 'Vérifier en utilisant un code de secours',
+ 'mfa_verify_backup_code' => 'Code de secours',
+ 'mfa_verify_backup_code_desc' => 'Entrez l\'un de vos codes de secours restants ci-dessous :',
+ 'mfa_verify_backup_code_enter_here' => 'Saisissez un code de secours ici',
+ 'mfa_verify_totp_desc' => 'Entrez ci-dessous le code généré à l\'aide de votre application mobile :',
+ 'mfa_setup_login_notification' => 'Méthode multi-facteurs configurée. Veuillez maintenant vous reconnecter en utilisant la méthode configurée.',
];
\ No newline at end of file
'view_all' => 'Tout afficher',
'create' => 'Créer',
'update' => 'Modifier',
- 'edit' => 'Editer',
+ 'edit' => 'Éditer',
'sort' => 'Trier',
'move' => 'Déplacer',
'copy' => 'Copier',
'reply' => 'Répondre',
'delete' => 'Supprimer',
'delete_confirm' => 'Confirmer la suppression',
- 'search' => 'Chercher',
+ 'search' => 'Rechercher',
'search_clear' => 'Réinitialiser la recherche',
'reset' => 'Réinitialiser',
'remove' => 'Enlever',
'add' => 'Ajouter',
+ 'configure' => 'Configurer',
'fullscreen' => 'Plein écran',
'favourite' => 'Favoris',
'unfavourite' => 'Supprimer des favoris',
'no_activity' => 'Aucune activité',
'no_items' => 'Aucun élément',
'back_to_top' => 'Retour en haut',
+ 'skip_to_main_content' => 'Passer au contenu principal',
'toggle_details' => 'Afficher les détails',
'toggle_thumbnails' => 'Afficher les vignettes',
'details' => 'Détails',
'image_upload_remove' => 'Supprimer',
// Code Editor
- 'code_editor' => 'Editer le code',
+ 'code_editor' => 'Éditer le code',
'code_language' => 'Langage du code',
'code_content' => 'Contenu du code',
'code_session_history' => 'Historique de session',
'meta_created_name' => 'Créé :timeLength par :user',
'meta_updated' => 'Mis à jour :timeLength',
'meta_updated_name' => 'Mis à jour :timeLength par :user',
- 'meta_owned_name' => 'Possédé par :user',
+ 'meta_owned_name' => 'Appartient à :user',
'entity_select' => 'Sélectionner l\'entité',
'images' => 'Images',
'my_recent_drafts' => 'Mes brouillons récents',
'my_recently_viewed' => 'Vus récemment',
- 'my_most_viewed_favourites' => 'Mes Favoris les plus vus',
+ 'my_most_viewed_favourites' => 'Mes favoris les plus vus',
'my_favourites' => 'Mes favoris',
'no_pages_viewed' => 'Vous n\'avez rien visité récemment',
'no_pages_recently_created' => 'Aucune page créée récemment',
'export_html' => 'Fichiers web',
'export_pdf' => 'Fichier PDF',
'export_text' => 'Document texte',
+ 'export_md' => 'Fichiers Markdown',
// Permissions and restrictions
'permissions' => 'Autorisations',
- 'permissions_intro' => 'Une fois activées ces permissions prendront la priorité sur tous les sets de permissions préexistants.',
+ '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_save' => 'Enregistrer les permissions',
'permissions_owner' => 'Propriétaire',
'shelves_empty' => 'Aucune étagère n\'a été créée',
'shelves_create' => 'Créer une nouvelle étagère',
'shelves_popular' => 'Étagères populaires',
- 'shelves_new' => 'Nouvelles Ã\89tagères',
- 'shelves_new_action' => 'Nouvelle Ã\89tagère',
+ 'shelves_new' => 'Nouvelles étagères',
+ 'shelves_new_action' => 'Nouvelle étagère',
'shelves_popular_empty' => 'Les étagères les plus populaires apparaîtront ici.',
'shelves_new_empty' => 'Les étagères les plus récentes apparaitront ici.',
'shelves_save' => 'Enregistrer l\'étagère',
'shelves_permissions' => 'Permissions de l\'étagère',
'shelves_permissions_updated' => 'Permissions de l\'étagère mises à jour',
'shelves_permissions_active' => 'Permissions de l\'étagère activées',
+ 'shelves_permissions_cascade_warning' => 'Les permissions sur les étagères ne sont pas automatiquement recopiées aux livres qu\'elles contiennent, car un livre peut exister dans plusieurs étagères. Les permissions peuvent cependant être recopiées vers les livres contenus en utilisant l\'option ci-dessous.',
'shelves_copy_permissions_to_books' => 'Copier les permissions vers les livres',
'shelves_copy_permissions' => 'Copier les permissions',
'shelves_copy_permissions_explain' => 'Ceci va appliquer les permissions actuelles de cette étagère à tous les livres qu\'elle contient. Avant de continuer, assurez-vous que toutes les permissions de cette étagère ont été sauvegardées.',
'books_empty_sort_current_book' => 'Trier les pages du livre',
'books_empty_add_chapter' => 'Ajouter un chapitre',
'books_permissions_active' => 'Permissions personnalisées activées',
- 'books_search_this' => 'Chercher dans le livre',
+ 'books_search_this' => 'Rechercher dans ce livre',
'books_navigation' => 'Navigation dans le livre',
'books_sort' => 'Trier les contenus du livre',
'books_sort_named' => 'Trier le livre :bookName',
'pages_popular' => 'Pages populaires',
'pages_new' => 'Nouvelle page',
'pages_attachments' => 'Fichiers joints',
- 'pages_navigation' => 'Navigation des pages',
+ 'pages_navigation' => 'Navigation dans la page',
'pages_delete' => 'Supprimer la page',
'pages_delete_named' => 'Supprimer la page :pageName',
'pages_delete_draft_named' => 'supprimer le brouillon de la page :pageName',
'pages_edit_draft' => 'Modifier le brouillon',
'pages_editing_draft' => 'Modification du brouillon',
'pages_editing_page' => 'Modification de la page',
- 'pages_edit_draft_save_at' => 'Brouillon sauvé le ',
+ 'pages_edit_draft_save_at' => 'Brouillon enregistré le ',
'pages_edit_delete_draft' => 'Supprimer le brouillon',
- 'pages_edit_discard_draft' => 'Ecarter le brouillon',
+ 'pages_edit_discard_draft' => 'Jeter le brouillon',
'pages_edit_set_changelog' => 'Remplir le journal des changements',
'pages_edit_enter_changelog_desc' => 'Entrez une brève description des changements effectués',
- 'pages_edit_enter_changelog' => 'Entrer dans le journal des changements',
- 'pages_save' => 'Enregistrez la page',
+ 'pages_edit_enter_changelog' => 'Ouvrir le journal des changements',
+ 'pages_save' => 'Enregistrer la page',
'pages_title' => 'Titre de la page',
'pages_name' => 'Nom de la page',
- 'pages_md_editor' => 'Editeur',
+ 'pages_md_editor' => 'Éditeur',
'pages_md_preview' => 'Prévisualisation',
'pages_md_insert_image' => 'Insérer une image',
'pages_md_insert_link' => 'Insérer un lien',
'pages_revisions_numbered_changes' => 'Modification #:id',
'pages_revisions_changelog' => 'Journal des changements',
'pages_revisions_changes' => 'Changements',
- 'pages_revisions_current' => 'Version courante',
+ 'pages_revisions_current' => 'Version actuelle',
'pages_revisions_preview' => 'Prévisualisation',
'pages_revisions_restore' => 'Restaurer',
'pages_revisions_none' => 'Cette page n\'a aucune révision',
'pages_permissions_active' => 'Permissions de page actives',
'pages_initial_revision' => 'Publication initiale',
'pages_initial_name' => 'Nouvelle page',
- 'pages_editing_draft_notification' => 'Vous éditez actuellement un brouillon qui a été sauvé :timeDiff.',
- 'pages_draft_edited_notification' => 'La page a été mise à jour depuis votre dernière visite. Vous devriez écarter ce brouillon.',
+ 'pages_editing_draft_notification' => 'Vous éditez actuellement un brouillon qui a été enregistré :timeDiff.',
+ 'pages_draft_edited_notification' => 'La page a été mise à jour depuis votre dernière visite. Vous devriez jeter ce brouillon.',
'pages_draft_edit_active' => [
'start_a' => ':count utilisateurs ont commencé à éditer cette page',
'start_b' => ':userName a commencé à éditer cette page',
'message' => ':start :time. Attention à ne pas écraser les mises à jour de quelqu\'un d\'autre !',
],
'pages_draft_discarded' => 'Brouillon écarté, la page est dans sa version actuelle.',
- 'pages_specific' => 'Page Spécifique',
+ 'pages_specific' => 'Page spécifique',
'pages_is_template' => 'Modèle de page',
// Editor Sidebar
'tag' => 'Mot-clé',
'tags' => 'Mots-clés',
'tag_name' => 'Nom du tag',
- 'tag_value' => 'Valeur du mot-clé (Optionnel)',
+ 'tag_value' => 'Valeur du mot-clé (optionnel)',
'tags_explain' => "Ajouter des mots-clés pour catégoriser votre contenu.",
'tags_add' => 'Ajouter un autre mot-clé',
- 'tags_remove' => 'Supprimer le tag',
+ 'tags_remove' => 'Supprimer le mot-clé',
'attachments' => 'Fichiers joints',
'attachments_explain' => 'Ajouter des fichiers ou des liens pour les afficher sur votre page. Ils seront affichés dans la barre latérale',
'attachments_explain_instant_save' => 'Ces changements sont enregistrés immédiatement.',
'attachments_delete' => 'Êtes-vous sûr de vouloir supprimer la pièce jointe ?',
'attachments_dropzone' => 'Glissez des fichiers ou cliquez ici pour attacher des fichiers',
'attachments_no_files' => 'Aucun fichier ajouté',
- 'attachments_explain_link' => 'Vous pouvez attacher un lien si vous ne souhaitez pas uploader un fichier.',
+ 'attachments_explain_link' => 'Vous pouvez ajouter un lien si vous ne souhaitez pas uploader un fichier.',
'attachments_link_name' => 'Nom du lien',
'attachment_link' => 'Lien de l\'attachement',
'attachments_link_url' => 'Lien sur un fichier',
'attachments_link_url_hint' => 'URL du site ou du fichier',
- 'attach' => 'Attacher',
- 'attachments_insert_link' => 'Ajouter un lien de pièce jointe à la page',
+ 'attach' => 'Ajouter',
+ 'attachments_insert_link' => 'Ajouter un lien à la page',
'attachments_edit_file' => 'Modifier le fichier',
'attachments_edit_file_name' => 'Nom du fichier',
'attachments_edit_drop_upload' => 'Glissez un fichier ou cliquer pour mettre à jour le fichier',
'templates_explain_set_as_template' => 'Vous pouvez définir cette page comme modèle pour que son contenu soit utilisé lors de la création d\'autres pages. Les autres utilisateurs pourront utiliser ce modèle s\'ils ont les permissions pour cette page.',
'templates_replace_content' => 'Remplacer le contenu de la page',
'templates_append_content' => 'Ajouter après le contenu de la page',
- 'templates_prepend_content' => 'Ajouter devant le contenu de la page',
+ 'templates_prepend_content' => 'Ajouter avant le contenu de la page',
// Profile View
'profile_user_for_x' => 'Utilisateur depuis :time',
'comment_deleted_success' => 'Commentaire supprimé',
'comment_created_success' => 'Commentaire ajouté',
'comment_updated_success' => 'Commentaire mis à jour',
- 'comment_delete_confirm' => 'Etes-vous sûr de vouloir supprimer ce commentaire ?',
+ 'comment_delete_confirm' => 'Êtes-vous sûr de vouloir supprimer ce commentaire ?',
'comment_in_reply_to' => 'En réponse à :commentId',
// Revision
'email_confirmation_awaiting' => 'L\'adresse e-mail du compte utilisé doit être confirmée',
'ldap_fail_anonymous' => 'L\'accès LDAP anonyme n\'a pas abouti',
'ldap_fail_authed' => 'L\'accès LDAP n\'a pas abouti avec cet utilisateur et ce mot de passe',
- 'ldap_extension_not_installed' => 'L\'extension LDAP PHP n\'est pas installée',
+ 'ldap_extension_not_installed' => 'L\'extension PHP LDAP n\'est pas installée',
'ldap_cannot_connect' => 'Impossible de se connecter au serveur LDAP, la connexion initiale a échoué',
'saml_already_logged_in' => 'Déjà connecté',
'saml_user_not_registered' => 'L\'utilisateur :name n\'est pas enregistré et l\'enregistrement automatique est désactivé',
'saml_no_email_address' => 'Impossible de trouver une adresse e-mail, pour cet utilisateur, dans les données fournies par le système d\'authentification externe',
'saml_invalid_response_id' => 'La requête du système d\'authentification externe n\'est pas reconnue par un processus démarré par cette application. Naviguer après une connexion peut causer ce problème.',
- 'saml_fail_authed' => 'Connexion avec :system échoue, le système n\'a pas fourni l\'autorisation réussie',
+ 'saml_fail_authed' => 'Connexion avec :system échouée, le système n\'a pas fourni l\'autorisation réussie',
'social_no_action_defined' => 'Pas d\'action définie',
'social_login_bad_response' => "Erreur pendant la tentative de connexion à :socialAccount : \n:error",
'social_account_in_use' => 'Ce compte :socialAccount est déjà utilisé. Essayez de vous connecter via :socialAccount.',
- 'social_account_email_in_use' => 'L\'email :email est déjà utilisé. Si vous avez déjà un compte :socialAccount, vous pouvez le joindre à votre profil existant.',
+ 'social_account_email_in_use' => 'L\'email :email est déjà utilisé. Si vous avez déjà un compte :socialAccount, vous pouvez le rattacher à votre profil existant.',
'social_account_existing' => 'Ce compte :socialAccount est déjà rattaché à votre profil.',
'social_account_already_used_existing' => 'Ce compte :socialAccount est déjà utilisé par un autre utilisateur.',
'social_account_not_used' => 'Ce compte :socialAccount n\'est lié à aucun utilisateur. ',
- 'social_account_register_instructions' => 'Si vous n\'avez pas encore de compte, vous pouvez le lier avec l\'option :socialAccount.',
- 'social_driver_not_found' => 'Pilote de compte social absent',
+ 'social_account_register_instructions' => 'Si vous n\'avez pas encore de compte, vous pouvez en créer un avec l\'option :socialAccount.',
+ 'social_driver_not_found' => 'Pilote de compte de réseaux sociaux absent',
'social_driver_not_configured' => 'Vos préférences pour le compte :socialAccount sont incorrectes.',
- 'invite_token_expired' => 'Le lien de cette invitation a expiré. Vous pouvez essayer de réinitiliser votre mot de passe.',
+ 'invite_token_expired' => 'Le lien de cette invitation a expiré. Vous pouvez essayer de réinitialiser votre mot de passe.',
// System
'path_not_writable' => 'Impossible d\'écrire dans :filePath. Assurez-vous d\'avoir les droits d\'écriture sur le serveur',
'server_upload_limit' => 'La taille du fichier est trop grande.',
'uploaded' => 'Le serveur n\'autorise pas l\'envoi d\'un fichier de cette taille. Veuillez essayer avec une taille de fichier réduite.',
'image_upload_error' => 'Une erreur est survenue pendant l\'envoi de l\'image',
- 'image_upload_type_error' => 'LE format de l\'image envoyée n\'est pas valide',
+ 'image_upload_type_error' => 'Le format de l\'image envoyée n\'est pas valide',
'file_upload_timeout' => 'Le téléchargement du fichier a expiré.',
// Attachments
'attachment_not_found' => 'Fichier joint non trouvé',
// Pages
- 'page_draft_autosave_fail' => 'Le brouillon n\'a pas pu être sauvé. Vérifiez votre connexion internet',
+ 'page_draft_autosave_fail' => 'Le brouillon n\'a pas pu être enregistré. Vérifiez votre connexion internet',
'page_custom_home_deletion' => 'Impossible de supprimer une page définie comme page d\'accueil',
// Entities
'chapter_not_found' => 'Chapitre non trouvé',
'selected_book_not_found' => 'Ce livre n\'a pas été trouvé',
'selected_book_chapter_not_found' => 'Ce livre ou chapitre n\'a pas été trouvé',
- 'guests_cannot_save_drafts' => 'Les invités ne peuvent pas sauver de brouillons',
+ 'guests_cannot_save_drafts' => 'Les invités ne peuvent pas enregistrer de brouillons',
// Users
- 'users_cannot_delete_only_admin' => 'Vous ne pouvez pas supprimer le dernier admin',
+ 'users_cannot_delete_only_admin' => 'Vous ne pouvez pas supprimer le dernier administrateur',
'users_cannot_delete_guest' => 'Vous ne pouvez pas supprimer l\'utilisateur invité',
// Roles
// Comments
'comment_list' => 'Une erreur s\'est produite lors de la récupération des commentaires.',
- 'cannot_add_comment_to_draft' => 'Vous ne pouvez pas ajouter de commentaires à un projet.',
+ 'cannot_add_comment_to_draft' => 'Vous ne pouvez pas ajouter de commentaires à un brouillon.',
'comment_add' => 'Une erreur s\'est produite lors de l\'ajout du commentaire.',
'comment_delete' => 'Une erreur s\'est produite lors de la suppression du commentaire.',
'empty_comment' => 'Impossible d\'ajouter un commentaire vide.',
// Error pages
'404_page_not_found' => 'Page non trouvée',
'sorry_page_not_found' => 'Désolé, cette page n\'a pas pu être trouvée.',
- 'sorry_page_not_found_permission_warning' => 'Si vous vous attendiez à ce que cette page existe, il se peut que vous n\'ayez pas l\'autorisation de la consulter.',
+ 'sorry_page_not_found_permission_warning' => 'Si cette page est censée exister, il se peut que vous n\'ayez pas l\'autorisation de la consulter.',
'image_not_found' => 'Image non trouvée',
'image_not_found_subtitle' => 'Désolé, l\'image que vous cherchez ne peut être trouvée.',
- 'image_not_found_details' => 'Si vous vous attendiez à ce que cette image existe, elle pourrait avoir été supprimée.',
+ 'image_not_found_details' => 'Si cette image était censée exister, il se pourrait qu\'elle ait été supprimée.',
'return_home' => 'Retour à l\'accueil',
'error_occurred' => 'Une erreur est survenue',
'app_down' => ':appName n\'est pas en service pour le moment',
'api_bad_authorization_format' => 'Un jeton d\'autorisation a été trouvé pour la requête, mais le format semble incorrect',
'api_user_token_not_found' => 'Aucun jeton API correspondant n\'a été trouvé pour le jeton d\'autorisation fourni',
'api_incorrect_token_secret' => 'Le secret fourni pour le jeton d\'API utilisé est incorrect',
- 'api_user_no_api_permission' => 'Le propriétaire du jeton API utilisé n\'a pas la permission de passer des appels API',
+ 'api_user_no_api_permission' => 'Le propriétaire du jeton API utilisé n\'a pas la permission de passer des requêtes API',
'api_user_token_expired' => 'Le jeton d\'autorisation utilisé a expiré',
// Settings & Maintenance
*/
return [
- 'password' => 'Les mots de passe doivent faire au moins 6 caractères et correspondre à la confirmation.',
+ 'password' => 'Les mots de passe doivent faire au moins 8 caractères et correspondre à la confirmation.',
'user' => "Nous n'avons pas trouvé d'utilisateur avec cette adresse.",
'token' => 'Le mot de passe reset du token n\'est pas valide pour cette adresse e-mail.',
- 'sent' => 'Nous vous avons envoyé un lien de réinitialisation de mot de passe !',
+ 'sent' => 'Nous vous avons envoyé un lien de réinitialisation de mot de passe par e-mail !',
'reset' => 'Votre mot de passe a été réinitialisé !',
];
'app_public_access_desc' => 'L\'activation de cette option permettra aux visiteurs, qui ne sont pas connectés, d\'accéder au contenu de votre instance BookStack.',
'app_public_access_desc_guest' => 'L\'accès pour les visiteurs publics peut être contrôlé par l\'utilisateur "Guest".',
'app_public_access_toggle' => 'Autoriser l\'accès public',
- 'app_public_viewing' => 'Accepter le visionnage public des pages ?',
- 'app_secure_images' => 'Activer l\'ajout d\'image sécurisé ?',
+ 'app_public_viewing' => 'Accepter l\'affichage public des pages ?',
+ 'app_secure_images' => 'Ajout d\'image sécurisé',
'app_secure_images_toggle' => 'Activer l\'ajout d\'image sécurisé',
'app_secure_images_desc' => 'Pour des questions de performances, toutes les images sont publiques. Cette option ajoute une chaîne aléatoire difficile à deviner dans les URLs des images.',
- 'app_editor' => 'Editeur des pages',
+ 'app_editor' => 'Éditeur des pages',
'app_editor_desc' => 'Sélectionnez l\'éditeur qui sera utilisé pour modifier les pages.',
'app_custom_html' => 'HTML personnalisé dans l\'en-tête',
'app_custom_html_desc' => 'Le contenu inséré ici sera ajouté en bas de la balise <head> de toutes les pages. Vous pouvez l\'utiliser pour ajouter du CSS personnalisé ou un tracker analytique.',
- 'app_custom_html_disabled_notice' => 'Le contenu de la tête HTML personnalisée est désactivé sur cette page de paramètres pour garantir que les modifications les plus récentes peuvent être annulées.',
- 'app_logo' => 'Logo de l\'Application',
+ 'app_custom_html_disabled_notice' => 'Le contenu de l\'en-tête HTML personnalisé est désactivé sur cette page de paramètres pour garantir que les modifications les plus récentes puissent être annulées.',
+ 'app_logo' => 'Logo de l\'application',
'app_logo_desc' => 'Cette image doit faire 43px de hauteur. <br>Les images plus larges seront réduites.',
'app_primary_color' => 'Couleur principale de l\'application',
'app_primary_color_desc' => 'Cela devrait être une valeur hexadécimale. <br>Laisser vide pour rétablir la couleur par défaut.',
'app_homepage_desc' => 'Choisissez une page à afficher sur la page d\'accueil au lieu de la vue par défaut. Les permissions sont ignorées pour les pages sélectionnées.',
'app_homepage_select' => 'Choisissez une page',
'app_footer_links' => 'Liens de pied de page',
- 'app_footer_links_desc' => 'Ajoutez des liens dans le pied de page du site. Ils seront affichés en bas de la plupart des pages, incluant celles qui ne nécesittent pas de connexion. Vous pouvez utiliser l\'étiquette "trans::<key>" pour utiliser les traductions définies par le système. Par exemple, utiliser "trans::common.privacy_policy" fournira la traduction de "Politique de Confidentalité" et "trans::common.terms_of_service" fournira la traduction de "Conditions d\'utilisation".',
+ 'app_footer_links_desc' => 'Ajouter des liens à afficher dans le pied de page du site. Ils seront affichés en bas de la plupart des pages, y compris celles qui ne nécessitent pas de connexion. Vous pouvez utiliser une étiquette de "trans::<key>" pour utiliser les traductions définies par le système. Par exemple : utiliser "trans::common.privacy_policy" fournira le texte traduit "Privacy Policy" et "trans::common.terms_of_service" fournira le texte traduit "Terms of Service".',
'app_footer_links_label' => 'Libellé du lien',
'app_footer_links_url' => 'URL du lien',
'app_footer_links_add' => 'Ajouter un lien en pied de page',
// Color settings
'content_colors' => 'Couleur du contenu',
'content_colors_desc' => 'Définit les couleurs pour tous les éléments de la hiérarchie d\'organisation des pages. Choisir les couleurs avec une luminosité similaire aux couleurs par défaut est recommandé pour la lisibilité.',
- 'bookshelf_color' => 'Couleur de l\'étagère',
- 'book_color' => 'Couleur du livre',
- 'chapter_color' => 'Couleur du chapitre',
- 'page_color' => 'Couleur de la page',
- 'page_draft_color' => 'Couleur du brouillon',
+ 'bookshelf_color' => 'Couleur des étagères',
+ 'book_color' => 'Couleur des livres',
+ 'chapter_color' => 'Couleur des chapitres',
+ 'page_color' => 'Couleur des pages',
+ 'page_draft_color' => 'Couleur des brouillons',
// Registration Settings
'reg_settings' => 'Préférence pour l\'inscription',
// Maintenance settings
'maint' => 'Maintenance',
'maint_image_cleanup' => 'Nettoyer les images',
- 'maint_image_cleanup_desc' => "Scan le contenu des pages et des révisions pour vérifier les images et les dessins en cours d'utilisation et lesquels sont redondant. Veuillez à faire une sauvegarde de la base de données et des images avant de lancer ceci.",
+ 'maint_image_cleanup_desc' => "Scanne le contenu des pages et des révisions pour vérifier les images, les dessins en cours d'utilisation et les doublons. Assurez-vous d'avoir une sauvegarde de la base de données et des images avant de lancer ceci.",
'maint_delete_images_only_in_revisions' => 'Supprimer également les images qui n\'existent que dans les anciennes révisions de page',
'maint_image_cleanup_run' => 'Lancer le nettoyage',
- 'maint_image_cleanup_warning' => ':count images potentiellement inutilisées trouvées. Etes-vous sûr de vouloir supprimer ces images ?',
+ 'maint_image_cleanup_warning' => ':count images potentiellement inutilisées trouvées. Êtes-vous sûr de vouloir supprimer ces images ?',
'maint_image_cleanup_success' => ':count images potentiellement inutilisées trouvées et supprimées !',
'maint_image_cleanup_nothing_found' => 'Aucune image inutilisée trouvée, rien à supprimer !',
- 'maint_send_test_email' => 'Envoyer un email de test',
+ 'maint_send_test_email' => 'Envoyer un e-mail de test',
'maint_send_test_email_desc' => 'Ceci envoie un e-mail de test à votre adresse e-mail spécifiée dans votre profil.',
- 'maint_send_test_email_run' => 'Envoyer un email de test',
- 'maint_send_test_email_success' => 'Email envoyé à :address',
- 'maint_send_test_email_mail_subject' => 'Email de test',
- 'maint_send_test_email_mail_greeting' => 'La livraison d\'email semble fonctionner !',
- 'maint_send_test_email_mail_text' => 'Félicitations ! Lorsque vous avez reçu cette notification par courriel, vos paramètres d\'email semblent être configurés correctement.',
+ 'maint_send_test_email_run' => 'Envoyer un e-mail de test',
+ 'maint_send_test_email_success' => 'E-mail envoyé à :address',
+ 'maint_send_test_email_mail_subject' => 'E-mail de test',
+ 'maint_send_test_email_mail_greeting' => 'L\'envoi d\'e-mail semble fonctionner !',
+ 'maint_send_test_email_mail_text' => 'Félicitations ! Comme vous avez bien reçu cette notification, vos paramètres d\'e-mail semblent être configurés correctement.',
'maint_recycle_bin_desc' => 'Les étagères, livres, chapitres et pages supprimés sont envoyés dans la corbeille afin qu\'ils puissent être restaurés ou supprimés définitivement. Les éléments plus anciens de la corbeille peuvent être supprimés automatiquement après un certain temps selon la configuration du système.',
'maint_recycle_bin_open' => 'Ouvrir la corbeille',
'recycle_bin' => 'Corbeille',
'recycle_bin_desc' => 'Ici, vous pouvez restaurer les éléments qui ont été supprimés ou choisir de les effacer définitivement du système. Cette liste n\'est pas filtrée contrairement aux listes d\'activités similaires dans le système pour lesquelles les filtres d\'autorisation sont appliqués.',
'recycle_bin_deleted_item' => 'Élément supprimé',
+ 'recycle_bin_deleted_parent' => 'Parent',
'recycle_bin_deleted_by' => 'Supprimé par',
'recycle_bin_deleted_at' => 'Date de suppression',
'recycle_bin_permanently_delete' => 'Supprimer définitivement',
'recycle_bin_restore' => 'Restaurer',
'recycle_bin_contents_empty' => 'La corbeille est vide',
- 'recycle_bin_empty' => 'Vider la Corbeille',
+ 'recycle_bin_empty' => 'Vider la corbeille',
'recycle_bin_empty_confirm' => 'Cela détruira définitivement tous les éléments de la corbeille, y compris le contenu contenu de chaque élément. Êtes-vous sûr de vouloir vider la corbeille ?',
'recycle_bin_destroy_confirm' => 'Cette action supprimera définitivement cet élément, ainsi que tous les éléments enfants listés ci-dessous du système et vous ne pourrez pas restaurer ce contenu. Êtes-vous sûr de vouloir supprimer définitivement cet élément ?',
'recycle_bin_destroy_list' => 'Éléments à détruire',
'recycle_bin_restore_list' => 'Éléments à restaurer',
'recycle_bin_restore_confirm' => 'Cette action restaurera l\'élément supprimé, y compris tous les éléments enfants, à leur emplacement d\'origine. Si l\'emplacement d\'origine a été supprimé depuis et est maintenant dans la corbeille, l\'élément parent devra également être restauré.',
'recycle_bin_restore_deleted_parent' => 'Le parent de cet élément a également été supprimé. Ceux-ci resteront supprimés jusqu\'à ce que ce parent soit également restauré.',
+ 'recycle_bin_restore_parent' => 'Restaurer le parent',
'recycle_bin_destroy_notification' => ':count éléments totaux supprimés de la corbeille.',
'recycle_bin_restore_notification' => ':count éléments totaux restaurés de la corbeille.',
'audit_deleted_item' => 'Élément supprimé',
'audit_deleted_item_name' => 'Nom: :name',
'audit_table_user' => 'Utilisateur',
- 'audit_table_event' => 'Evènement',
- 'audit_table_related' => 'Élément ou détail lié',
- 'audit_table_date' => 'Date d\'activation',
+ 'audit_table_event' => 'Événement',
+ 'audit_table_related' => 'Élément concerné ou action réalisée',
+ 'audit_table_ip' => 'Adresse IP',
+ 'audit_table_date' => 'Horodatage',
'audit_date_from' => 'À partir du',
'audit_date_to' => 'Jusqu\'au',
'role_details' => 'Détails du rôle',
'role_name' => 'Nom du rôle',
'role_desc' => 'Courte description du rôle',
+ 'role_mfa_enforced' => 'Nécessite une authentification multi-facteurs',
'role_external_auth_id' => 'Identifiants d\'authentification externes',
'role_system' => 'Permissions système',
'role_manage_users' => 'Gérer les utilisateurs',
'role_manage_page_templates' => 'Gérer les modèles de page',
'role_access_api' => 'Accès à l\'API du système',
'role_manage_settings' => 'Gérer les préférences de l\'application',
+ 'role_export_content' => 'Exporter le contenu',
'role_asset' => 'Permissions des ressources',
- 'roles_system_warning' => 'Sachez que l\'accès à l\'une des trois permissions ci-dessus peut permettre à un utilisateur de modifier ses propres privilèges ou les privilèges des autres utilisateurs du système. Attribuer uniquement des rôles avec ces permissions à des utilisateurs de confiance.',
+ 'roles_system_warning' => 'Sachez que l\'accès à l\'une des trois permissions ci-dessus peut permettre à un utilisateur de modifier ses propres privilèges ou les privilèges des autres utilisateurs du système. N\'attribuez uniquement des rôles avec ces permissions qu\'à des utilisateurs de confiance.',
'role_asset_desc' => 'Ces permissions contrôlent l\'accès par défaut des ressources dans le système. Les permissions dans les livres, les chapitres et les pages ignoreront ces permissions',
'role_asset_admins' => 'Les administrateurs ont automatiquement accès à tous les contenus mais les options suivantes peuvent afficher ou masquer certaines options de l\'interface.',
'role_all' => 'Tous',
'users' => 'Utilisateurs',
'user_profile' => 'Profil d\'utilisateur',
'users_add_new' => 'Ajouter un nouvel utilisateur',
- 'users_search' => 'Chercher les utilisateurs',
+ 'users_search' => 'Rechercher les utilisateurs',
'users_latest_activity' => 'Dernière activité',
'users_details' => 'Informations de l\'utilisateur',
'users_details_desc' => 'Définissez un nom et une adresse e-mail pour cet utilisateur. L\'adresse e-mail sera utilisée pour se connecter à l\'application.',
'users_role' => 'Rôles de l\'utilisateur',
'users_role_desc' => 'Sélectionnez les rôles auxquels cet utilisateur sera affecté. Si un utilisateur est affecté à plusieurs rôles, les permissions de ces rôles s\'empileront et ils recevront toutes les capacités des rôles affectés.',
'users_password' => 'Mot de passe de l\'utilisateur',
- 'users_password_desc' => 'Définissez un mot de passe utilisé pour vous connecter à l\'application. Il doit comporter au moins 5 caractères.',
- 'users_send_invite_text' => 'Vous pouvez choisir d\'envoyer à cet utilisateur un email d\'invitation qui lui permet de définir son propre mot de passe, sinon vous pouvez définir son mot de passe vous-même.',
+ 'users_password_desc' => 'Définissez un mot de passe utilisé pour vous connecter à l\'application. Il doit comporter au moins 6 caractères.',
+ 'users_send_invite_text' => 'Vous pouvez choisir d\'envoyer à cet utilisateur un e-mail d\'invitation qui lui permet de définir son propre mot de passe, sinon vous pouvez définir son mot de passe vous-même.',
'users_send_invite_option' => 'Envoyer l\'e-mail d\'invitation',
'users_external_auth_id' => 'Identifiant d\'authentification externe',
'users_external_auth_id_desc' => 'C\'est l\'ID utilisé pour correspondre à cet utilisateur lors de la communication avec votre système d\'authentification externe.',
- 'users_password_warning' => 'Remplissez ce formulaire uniquement si vous souhaitez changer de mot de passe:',
+ 'users_password_warning' => 'Remplissez ce formulaire uniquement si vous souhaitez changer de mot de passe :',
'users_system_public' => 'Cet utilisateur représente les invités visitant votre instance. Il est assigné automatiquement aux invités.',
'users_delete' => 'Supprimer un utilisateur',
'users_delete_named' => 'Supprimer l\'utilisateur :userName',
'users_delete_warning' => 'Ceci va supprimer \':userName\' du système.',
'users_delete_confirm' => 'Êtes-vous sûr(e) de vouloir supprimer cet utilisateur ?',
- 'users_migrate_ownership' => 'Migré propriété',
+ 'users_migrate_ownership' => 'Transférer la propriété',
'users_migrate_ownership_desc' => 'Sélectionnez un utilisateur ici si vous voulez qu\'un autre utilisateur devienne le propriétaire de tous les éléments actuellement détenus par cet utilisateur.',
- 'users_none_selected' => 'Aucun utilisateur n\'a été séléctionné',
+ 'users_none_selected' => 'Aucun utilisateur n\'a été sélectionné',
'users_delete_success' => 'Utilisateur supprimé avec succès',
'users_edit' => 'Modifier l\'utilisateur',
'users_edit_profile' => 'Modifier le profil',
'users_avatar_desc' => 'Cette image doit être un carré d\'environ 256 px.',
'users_preferred_language' => 'Langue préférée',
'users_preferred_language_desc' => 'Cette option changera la langue utilisée pour l\'interface utilisateur de l\'application. Ceci n\'affectera aucun contenu créé par l\'utilisateur.',
- 'users_social_accounts' => 'Comptes sociaux',
+ 'users_social_accounts' => 'Réseaux sociaux',
'users_social_accounts_info' => 'Vous pouvez connecter des réseaux sociaux à votre compte pour vous connecter plus rapidement. Déconnecter un compte n\'enlèvera pas les accès autorisés précédemment sur votre compte de réseau social.',
'users_social_connect' => 'Connecter le compte',
'users_social_disconnect' => 'Déconnecter le compte',
'users_social_connected' => 'Votre compte :socialAccount a été ajouté avec succès.',
'users_social_disconnected' => 'Votre compte :socialAccount a été déconnecté avec succès',
- 'users_api_tokens' => 'Jetons de l\'API',
+ 'users_api_tokens' => 'Jetons API',
'users_api_tokens_none' => 'Aucun jeton API n\'a été créé pour cet utilisateur',
'users_api_tokens_create' => 'Créer un jeton',
'users_api_tokens_expires' => 'Expiré',
'users_api_tokens_docs' => 'Documentation de l\'API',
+ 'users_mfa' => 'Authentification multi-facteurs',
+ 'users_mfa_desc' => 'Configurer l\'authentification multi-facteurs ajoute une couche supplémentaire de sécurité à votre compte utilisateur.',
+ 'users_mfa_x_methods' => ':count méthode configurée|:count méthodes configurées',
+ 'users_mfa_configure' => 'Méthode de configuration',
// API Tokens
'user_api_token_create' => 'Créer un nouveau jeton API',
'user_api_token_expiry' => 'Date d\'expiration',
'user_api_token_expiry_desc' => 'Définissez une date à laquelle ce jeton expire. Après cette date, les demandes effectuées à l\'aide de ce jeton ne fonctionneront plus. Le fait de laisser ce champ vide entraînera une expiration dans 100 ans.',
'user_api_token_create_secret_message' => 'Immédiatement après la création de ce jeton, un "ID de jeton" "et" Secret de jeton "sera généré et affiché. Le secret ne sera affiché qu\'une seule fois, alors assurez-vous de copier la valeur dans un endroit sûr et sécurisé avant de continuer.',
- 'user_api_token_create_success' => 'L\'API token a été créé avec succès',
- 'user_api_token_update_success' => 'L\'API token a été mis à jour avec succès',
- 'user_api_token' => 'Token API',
+ 'user_api_token_create_success' => 'Le jeton API a été créé avec succès',
+ 'user_api_token_update_success' => 'Le jeton API a été mis à jour avec succès',
+ 'user_api_token' => 'Jeton API',
'user_api_token_id' => 'Token ID',
'user_api_token_id_desc' => 'Il s\'agit d\'un identifiant généré par le système non modifiable pour ce jeton qui devra être fourni dans les demandes d\'API.',
'user_api_token_secret' => 'Token Secret',
'user_api_token_secret_desc' => 'Il s\'agit d\'un secret généré par le système pour ce jeton, qui devra être fourni dans les demandes d\'API. Cela ne sera affiché qu\'une seule fois, alors copiez cette valeur dans un endroit sûr et sécurisé.',
'user_api_token_created' => 'Jeton créé :timeAgo',
'user_api_token_updated' => 'Jeton mis à jour :timeAgo',
- 'user_api_token_delete' => 'Supprimer le Token',
+ 'user_api_token_delete' => 'Supprimer le jeton',
'user_api_token_delete_warning' => 'Cela supprimera complètement le jeton d\'API avec le nom \':tokenName\'.',
- 'user_api_token_delete_confirm' => 'Souhaitez-vous vraiment effacer l\'API Token ?',
- 'user_api_token_delete_success' => 'L\'API token a été supprimé avec succès',
+ 'user_api_token_delete_confirm' => 'Souhaitez-vous vraiment effacer ce jeton API ?',
+ 'user_api_token_delete_success' => 'Le jeton API a été supprimé avec succès',
//! If editing translations files directly please ignore this in all
//! languages apart from en. Content will be auto-copied from en.
'it' => 'Italian',
'ja' => '日本語',
'ko' => '한국어',
+ 'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nl' => 'Nederlands',
'nb' => 'Norvegien',
'alpha_dash' => ':attribute doit contenir uniquement des lettres, chiffres et traits d\'union.',
'alpha_num' => ':attribute doit contenir uniquement des chiffres et des lettres.',
'array' => ':attribute doit être un tableau.',
+ 'backup_codes' => 'Le code fourni n\'est pas valide ou a déjà été utilisé.',
'before' => ':attribute doit être inférieur à :date.',
'between' => [
'numeric' => ':attribute doit être compris entre :min et :max.',
- 'file' => ':attribute doit être compris entre :min et :max kilobytes.',
+ 'file' => ':attribute doit être compris entre :min et :max Ko.',
'string' => ':attribute doit être compris entre :min et :max caractères.',
'array' => ':attribute doit être compris entre :min et :max éléments.',
],
'filled' => ':attribute est un champ requis.',
'gt' => [
'numeric' => ':attribute doit être plus grand que :value.',
- 'file' => ':attribute doit être plus grand que :value kilobytes.',
+ 'file' => ':attribute doit être plus grand que :value Ko.',
'string' => ':attribute doit être plus grand que :value caractères.',
'array' => ':attribute doit avoir plus que :value éléments.',
],
'gte' => [
'numeric' => ':attribute doit être plus grand ou égal à :value.',
- 'file' => ':attribute doit être plus grand ou égal à :value kilobytes.',
+ 'file' => ':attribute doit être plus grand ou égal à :value Ko.',
'string' => ':attribute doit être plus grand ou égal à :value caractères.',
'array' => ':attribute doit avoir :value éléments ou plus.',
],
'ip' => ':attribute doit être une adresse IP valide.',
'ipv4' => ':attribute doit être une adresse IPv4 valide.',
'ipv6' => ':attribute doit être une adresse IPv6 valide.',
- 'json' => ':attribute doit être une chaine JSON valide.',
+ 'json' => ':attribute doit être une chaîne JSON valide.',
'lt' => [
'numeric' => ':attribute doit être plus petit que :value.',
- 'file' => ':attribute doit être plus petit que :value kilobytes.',
+ 'file' => ':attribute doit être plus petit que :value Ko.',
'string' => ':attribute doit être plus petit que :value caractères.',
'array' => ':attribute doit avoir moins de :value éléments.',
],
'lte' => [
'numeric' => ':attribute doit être plus petit ou égal à :value.',
- 'file' => ':attribute doit être plus petit ou égal à :value kilobytes.',
+ 'file' => ':attribute doit être plus petit ou égal à :value Ko.',
'string' => ':attribute doit être plus petit ou égal à :value caractères.',
'array' => ':attribute ne doit pas avoir plus de :value éléments.',
],
'max' => [
'numeric' => ':attribute ne doit pas excéder :max.',
- 'file' => ':attribute ne doit pas excéder :max kilobytes.',
+ 'file' => ':attribute ne doit pas excéder :max Ko.',
'string' => ':attribute ne doit pas excéder :max caractères.',
'array' => ':attribute ne doit pas contenir plus de :max éléments.',
],
],
'string' => ':attribute doit être une chaîne de caractères.',
'timezone' => ':attribute doit être une zone valide.',
+ 'totp' => 'Le code fourni n\'est pas valide ou est expiré.',
'unique' => ':attribute est déjà utilisé.',
'url' => ':attribute a un format invalide.',
'uploaded' => 'Le fichier n\'a pas pu être envoyé. Le serveur peut ne pas accepter des fichiers de cette taille.',
'favourite_add_notification' => '":name" has been added to your favourites',
'favourite_remove_notification' => '":name" has been removed from your favourites',
+ // MFA
+ 'mfa_setup_method_notification' => 'Multi-factor method successfully configured',
+ 'mfa_remove_method_notification' => 'Multi-factor method successfully removed',
+
// Other
'commented_on' => 'commented on',
'permissions_update' => 'updated permissions',
'user_invite_page_welcome' => 'Welcome to :appName!',
'user_invite_page_text' => 'To finalise your account and gain access you need to set a password which will be used to log-in to :appName on future visits.',
'user_invite_page_confirm_button' => 'Confirm Password',
- 'user_invite_success' => 'Password set, you now have access to :appName!'
+ 'user_invite_success' => 'Password set, you now have access to :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_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.',
];
\ No newline at end of file
'reset' => 'איפוס',
'remove' => 'הסר',
'add' => 'הוסף',
+ 'configure' => 'Configure',
'fullscreen' => 'Fullscreen',
'favourite' => 'Favourite',
'unfavourite' => 'Unfavourite',
'no_activity' => 'אין פעילות להציג',
'no_items' => 'אין פריטים זמינים',
'back_to_top' => 'בחזרה ללמעלה',
+ 'skip_to_main_content' => 'Skip to main content',
'toggle_details' => 'הצג/הסתר פרטים',
'toggle_thumbnails' => 'הצג/הסתר תמונות',
'details' => 'פרטים',
'export_html' => 'דף אינטרנט',
'export_pdf' => 'קובץ PDF',
'export_text' => 'טקסט רגיל',
+ 'export_md' => 'Markdown File',
// Permissions and restrictions
'permissions' => 'הרשאות',
'shelves_permissions' => 'הרשאות מדף',
'shelves_permissions_updated' => 'הרשאות מדף עודכנו',
'shelves_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' => 'פעולה זו תעתיק את כל הרשאות המדף לכל הספרים המשוייכים למדף זה. לפני הביצוע, יש לוודא שכל הרשאות המדף אכן נשמרו.',
'recycle_bin' => 'סל המיחזור',
'recycle_bin_desc' => 'כאן תוכלו לאחזר פריטים שנמחקו או לבחור למחוק אותם מהמערכת לצמיתות. רשימה זו לא מסוננת, בשונה מרשימות פעילות דומות במערכת, בהן מוחלים מסנני הרשאות.',
'recycle_bin_deleted_item' => 'פריט שנמחק',
+ 'recycle_bin_deleted_parent' => 'Parent',
'recycle_bin_deleted_by' => 'נמחק על ידי',
'recycle_bin_deleted_at' => 'זמן המחיקה',
'recycle_bin_permanently_delete' => 'מחק לצמיתות',
'recycle_bin_restore_list' => 'פריטים שיאוחזרו',
'recycle_bin_restore_confirm' => 'פעולה זו תאחזר את הפריט שנמחק, לרבות רכיבי-הבן שלו, למיקומו המקורי. אם המיקום המקורי נמחק מאז, וכעת נמצא בסל המיחזור, יש לאחזר גם את פריט-האב.',
'recycle_bin_restore_deleted_parent' => 'פריט-האב של פריט זה נמחק. פריטים אלה יישארו מחוקים עד שפריט-אב זה יאוחזר.',
+ 'recycle_bin_restore_parent' => 'Restore Parent',
'recycle_bin_destroy_notification' => 'נמחקו בסה"כ :count פריטים מסל המיחזור.',
'recycle_bin_restore_notification' => 'אוחזרו בסה"כ :count פריטים מסל המיחזור.',
'audit_table_user' => 'משתמש',
'audit_table_event' => 'אירוע',
'audit_table_related' => 'פריט או פרט קשור',
+ 'audit_table_ip' => 'IP Address',
'audit_table_date' => 'זמן הפעילות',
'audit_date_from' => 'טווח תאריכים החל מ...',
'audit_date_to' => 'טווח תאריכים עד ל...',
'role_details' => 'פרטי תפקיד',
'role_name' => 'שם התפקיד',
'role_desc' => 'תיאור קצר של התפקיד',
+ 'role_mfa_enforced' => 'Requires Multi-Factor Authentication',
'role_external_auth_id' => 'ID-י אותנטיקציה חיצוניים',
'role_system' => 'הרשאות מערכת',
'role_manage_users' => 'ניהול משתמשים',
'role_manage_page_templates' => 'נהל תבניות דפים',
'role_access_api' => 'גש ל-API המערכת',
'role_manage_settings' => 'ניהול הגדרות יישום',
+ 'role_export_content' => 'Export content',
'role_asset' => 'הרשאות משאבים',
'roles_system_warning' => 'שימו לב לכך שגישה לכל אחת משלושת ההרשאות הנ"ל יכולה לאפשר למשתמש לשנות את הפריווילגיות שלהם או של אחרים במערכת. הגדירו תפקידים להרשאות אלה למשתמשים בהם אתם בוטחים בלבד.',
'role_asset_desc' => 'הרשאות אלו שולטות בגישת ברירת המחדל למשאבים בתוך המערכת. הרשאות של ספרים, פרקים ודפים יגברו על הרשאות אלו.',
'users_api_tokens_create' => 'צור אסימון',
'users_api_tokens_expires' => 'פג',
'users_api_tokens_docs' => 'תיעוד API',
+ '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',
// API Tokens
'user_api_token_create' => 'צור אסימון API',
'it' => 'Italian',
'ja' => '日本語',
'ko' => '한국어',
+ 'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nl' => 'Nederlands',
'nb' => 'Norsk (Bokmål)',
'alpha_dash' => 'שדה :attribute יכול להכיל אותיות, מספרים ומקפים בלבד.',
'alpha_num' => 'שדה :attribute יכול להכיל אותיות ומספרים בלבד.',
'array' => 'שדה :attribute חייב להיות מערך.',
+ 'backup_codes' => 'The provided code is not valid or has already been used.',
'before' => 'שדה :attribute חייב להיות תאריך לפני :date.',
'between' => [
'numeric' => 'שדה :attribute חייב להיות בין :min ל-:max.',
],
'string' => 'שדה :attribute חייב להיות מחרוזת.',
'timezone' => 'שדה :attribute חייב להיות איזור תקני.',
+ 'totp' => 'The provided code is not valid or has expired.',
'unique' => 'שדה :attribute כבר תפוס.',
'url' => 'שדה :attribute בעל פורמט שאינו תקין.',
'uploaded' => 'שדה :attribute ארעה שגיאה בעת ההעלאה.',
'favourite_add_notification' => '":name" has been added to your favourites',
'favourite_remove_notification' => '":name" has been removed from your favourites',
+ // MFA
+ 'mfa_setup_method_notification' => 'Multi-factor method successfully configured',
+ 'mfa_remove_method_notification' => 'Multi-factor method successfully removed',
+
// Other
'commented_on' => 'komentirano',
'permissions_update' => 'ažurirana dopuštenja',
'user_invite_page_welcome' => 'Dobrodošli u :appName!',
'user_invite_page_text' => 'Da biste postavili račun i dobili pristup trebate unijeti lozinku kojom ćete se ubuduće prijaviti na :appName.',
'user_invite_page_confirm_button' => 'Potvrdite lozinku',
- 'user_invite_success' => 'Lozinka je postavljena, možete pristupiti :appName!'
+ 'user_invite_success' => 'Lozinka je postavljena, možete pristupiti :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_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.',
];
\ No newline at end of file
'reset' => 'Ponovno postavi',
'remove' => 'Ukloni',
'add' => 'Dodaj',
+ 'configure' => 'Configure',
'fullscreen' => 'Cijeli zaslon',
'favourite' => 'Favourite',
'unfavourite' => 'Unfavourite',
'no_activity' => 'Nema aktivnosti za pregled',
'no_items' => 'Nedostupno',
'back_to_top' => 'Natrag na vrh',
+ 'skip_to_main_content' => 'Skip to main content',
'toggle_details' => 'Prebaci detalje',
'toggle_thumbnails' => 'Uključi minijature',
'details' => 'Detalji',
'export_html' => 'Web File',
'export_pdf' => 'PDF File',
'export_text' => 'Text File',
+ 'export_md' => 'Markdown File',
// Permissions and restrictions
'permissions' => 'Dopuštenja',
'shelves_permissions' => 'Dopuštenja za policu',
'shelves_permissions_updated' => 'Ažurirana dopuštenja za policu',
'shelves_permissions_active' => 'Aktivirana dopuštenja za policu',
+ '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' => 'Kopiraj dopuštenja za knjige',
'shelves_copy_permissions' => 'Kopiraj dopuštenja',
'shelves_copy_permissions_explain' => 'Ovo će promijeniti trenutna dopuštenja za policu i knjige u njoj. Prije aktivacije provjerite jesu li sve dopuštenja za ovu policu spremljena.',
'recycle_bin' => 'Recycle Bin',
'recycle_bin_desc' => 'Ovdje možete vratiti izbrisane stavke ili ih trajno ukloniti iz sustava. Popis nije filtriran kao što su to popisi u kojima su omogućeni filteri.',
'recycle_bin_deleted_item' => 'Izbrisane stavke',
+ 'recycle_bin_deleted_parent' => 'Parent',
'recycle_bin_deleted_by' => 'Izbrisano od',
'recycle_bin_deleted_at' => 'Vrijeme brisanja',
'recycle_bin_permanently_delete' => 'Trajno izbrisano',
'recycle_bin_restore_list' => 'Stavke koje treba vratiti',
'recycle_bin_restore_confirm' => 'Ova radnja vraća izbrisane stavke i njene podređene elemente na prvobitnu lokaciju. Ako je nadređena stavka izbrisana i nju treba vratiti.',
'recycle_bin_restore_deleted_parent' => 'S obzirom da je nadređena stavka obrisana najprije treba vratiti nju.',
+ 'recycle_bin_restore_parent' => 'Restore Parent',
'recycle_bin_destroy_notification' => 'Ukupno izbrisane :count stavke iz Recycle Bin',
'recycle_bin_restore_notification' => 'Ukupno vraćene :count stavke iz Recycle Bin',
'audit_table_user' => 'Korisnik',
'audit_table_event' => 'Događaj',
'audit_table_related' => 'Povezana stavka ili detalj',
+ 'audit_table_ip' => 'IP Address',
'audit_table_date' => 'Datum aktivnosti',
'audit_date_from' => 'Rangiraj datum od',
'audit_date_to' => 'Rangiraj datum do',
'role_details' => 'Detalji uloge',
'role_name' => 'Ime uloge',
'role_desc' => 'Kratki opis uloge',
+ 'role_mfa_enforced' => 'Requires Multi-Factor Authentication',
'role_external_auth_id' => 'Autorizacija',
'role_system' => 'Dopuštenja sustava',
'role_manage_users' => 'Upravljanje korisnicima',
'role_manage_page_templates' => 'Upravljanje predlošcima stranica',
'role_access_api' => 'API pristup',
'role_manage_settings' => 'Upravljanje postavkama aplikacija',
+ 'role_export_content' => 'Export content',
'role_asset' => 'Upravljanje vlasništvom',
'roles_system_warning' => 'Uzmite u obzir da pristup bilo kojem od ovih dopuštenja dozvoljavate korisniku upravljanje dopuštenjima ostalih u sustavu. Ova dopuštenja dodijelite pouzdanim korisnicima.',
'role_asset_desc' => 'Ova dopuštenja kontroliraju zadane pristupe. Dopuštenja za knjige, poglavlja i stranice ih poništavaju.',
'users_api_tokens_create' => 'Stvori token',
'users_api_tokens_expires' => 'Isteklo',
'users_api_tokens_docs' => 'API dokumentacija',
+ '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',
// API Tokens
'user_api_token_create' => 'Stvori API token',
'it' => 'Italian',
'ja' => '日本語',
'ko' => '한국어',
+ 'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nl' => 'Nederlands',
'nb' => 'Norsk (Bokmål)',
'alpha_dash' => ':attribute može sadržavati samo slova, brojeve, crtice i donje crtice.',
'alpha_num' => ':attribute može sadržavati samo slova i brojeve.',
'array' => ':attribute mora biti niz.',
+ 'backup_codes' => 'The provided code is not valid or has already been used.',
'before' => ':attribute mora biti prije :date.',
'between' => [
'numeric' => ':attribute mora biti između :min i :max.',
],
'string' => ':attribute mora biti niz.',
'timezone' => ':attribute mora biti valjan.',
+ 'totp' => 'The provided code is not valid or has expired.',
'unique' => ':attribute se već koristi.',
'url' => 'Format :attribute nije valjan.',
'uploaded' => 'Datoteka se ne može prenijeti. Server možda ne prihvaća datoteke te veličine.',
'favourite_add_notification' => '":name" has been added to your favourites',
'favourite_remove_notification' => '":name" has been removed from your favourites',
+ // MFA
+ 'mfa_setup_method_notification' => 'Multi-factor method successfully configured',
+ 'mfa_remove_method_notification' => 'Multi-factor method successfully removed',
+
// Other
'commented_on' => 'megjegyzést fűzött hozzá:',
'permissions_update' => 'updated permissions',
'user_invite_page_welcome' => ':appName üdvözöl!',
'user_invite_page_text' => 'A fiók véglegesítéséhez és a hozzáféréshez be kell állítani egy jelszót ami :appName weboldalon lesz használva a bejelentkezéshez.',
'user_invite_page_confirm_button' => 'Jelszó megerősítése',
- 'user_invite_success' => 'Jelszó beállítva, :appName most már elérhető!'
+ 'user_invite_success' => 'Jelszó beállítva, :appName most már elérhető!',
+
+ // 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_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.',
];
\ No newline at end of file
'reset' => 'Visszaállítás',
'remove' => 'Eltávolítás',
'add' => 'Hozzáadás',
+ 'configure' => 'Configure',
'fullscreen' => 'Teljes képernyő',
'favourite' => 'Favourite',
'unfavourite' => 'Unfavourite',
'no_activity' => 'Nincs megjeleníthető aktivitás',
'no_items' => 'Nincsenek elérhető elemek',
'back_to_top' => 'Oldal eleje',
+ 'skip_to_main_content' => 'Skip to main content',
'toggle_details' => 'Részletek átkapcsolása',
'toggle_thumbnails' => 'Bélyegképek átkapcsolása',
'details' => 'Részletek',
'export_html' => 'Webfájlt tartalmaz',
'export_pdf' => 'PDF fájl',
'export_text' => 'Egyszerű szövegfájl',
+ 'export_md' => 'Markdown File',
// Permissions and restrictions
'permissions' => 'Jogosultságok',
'shelves_permissions' => 'Könyvespolc jogosultság',
'shelves_permissions_updated' => 'Könyvespolc jogosultságok frissítve',
'shelves_permissions_active' => 'Könyvespolc jogosultságok aktívak',
+ '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' => 'Jogosultság másolása könyvekre',
'shelves_copy_permissions' => 'Jogosultság másolása',
'shelves_copy_permissions_explain' => 'Ez alkalmazni fogja ennek a könyvespolcnak az aktuális jogosultság beállításait az összes benne található könyvön. Aktiválás előtt ellenőrizni kell, hogy a könyvespolc jogosultságain végzett összes módosítás el lett mentve.',
'recycle_bin' => 'Lomtár',
'recycle_bin_desc' => 'Here you can restore items that have been deleted or choose to permanently remove them from the system. This list is unfiltered unlike similar activity lists in the system where permission filters are applied.',
'recycle_bin_deleted_item' => 'Törölt elem',
+ 'recycle_bin_deleted_parent' => 'Parent',
'recycle_bin_deleted_by' => 'Törölte',
'recycle_bin_deleted_at' => 'Törlés ideje',
'recycle_bin_permanently_delete' => 'Permanently Delete',
'recycle_bin_restore_list' => 'Items to be Restored',
'recycle_bin_restore_confirm' => 'This action will restore the deleted item, including any child elements, to their original location. If the original location has since been deleted, and is now in the recycle bin, the parent item will also need to be restored.',
'recycle_bin_restore_deleted_parent' => 'The parent of this item has also been deleted. These will remain deleted until that parent is also restored.',
+ 'recycle_bin_restore_parent' => 'Restore Parent',
'recycle_bin_destroy_notification' => 'Deleted :count total items from the recycle bin.',
'recycle_bin_restore_notification' => 'Restored :count total items from the recycle bin.',
'audit_table_user' => 'Felhasználó',
'audit_table_event' => 'Esemény',
'audit_table_related' => 'Related Item or Detail',
+ 'audit_table_ip' => 'IP Address',
'audit_table_date' => 'Activity Date',
'audit_date_from' => 'Date Range From',
'audit_date_to' => 'Date Range To',
'role_details' => 'Szerepkör részletei',
'role_name' => 'Szerepkör neve',
'role_desc' => 'Szerepkör rövid leírása',
+ 'role_mfa_enforced' => 'Requires Multi-Factor Authentication',
'role_external_auth_id' => 'Külső hitelesítés azonosítók',
'role_system' => 'Rendszer jogosultságok',
'role_manage_users' => 'Felhasználók kezelése',
'role_manage_page_templates' => 'Oldalsablonok kezelése',
'role_access_api' => 'Hozzáférés a rendszer API-hoz',
'role_manage_settings' => 'Alkalmazás beállításainak kezelése',
+ 'role_export_content' => 'Export content',
'role_asset' => 'Eszköz jogosultságok',
'roles_system_warning' => 'Be aware that access to any of the above three permissions can allow a user to alter their own privileges or the privileges of others in the system. Only assign roles with these permissions to trusted users.',
'role_asset_desc' => 'Ezek a jogosultság vezérlik a alapértelmezés szerinti hozzáférést a rendszerben található eszközökhöz. A könyvek, fejezetek és oldalak jogosultságai felülírják ezeket a jogosultságokat.',
'users_api_tokens_create' => 'Vezérjel létrehozása',
'users_api_tokens_expires' => 'Lejárat',
'users_api_tokens_docs' => 'API dokumentáció',
+ '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',
// API Tokens
'user_api_token_create' => 'API vezérjel létrehozása',
'it' => 'Italian',
'ja' => '日本語',
'ko' => '한국어',
+ 'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nl' => 'Nederlands',
'nb' => 'Norsk (Bokmål)',
'alpha_dash' => ':attribute csak betűket, számokat és kötőjeleket tartalmazhat.',
'alpha_num' => ':attribute csak betűket és számokat tartalmazhat.',
'array' => ':attribute tömb kell legyen.',
+ 'backup_codes' => 'The provided code is not valid or has already been used.',
'before' => ':attribute dátumnak :date előttinek kell lennie.',
'between' => [
'numeric' => ':attribute értékének :min és :max között kell lennie.',
],
'string' => ':attribute karaktersorozatnak kell legyen.',
'timezone' => ':attribute érvényes zóna kell legyen.',
+ 'totp' => 'The provided code is not valid or has expired.',
'unique' => ':attribute már elkészült.',
'url' => ':attribute formátuma érvénytelen.',
'uploaded' => 'A fájlt nem lehet feltölteni. A kiszolgáló nem fogad el ilyen méretű fájlokat.',
// Pages
'page_create' => 'telah membuat halaman',
'page_create_notification' => 'Halaman Berhasil dibuat',
- 'page_update' => 'halaman diperbaharui',
- 'page_update_notification' => 'Halaman Berhasil Diperbarui',
+ 'page_update' => 'halaman telah diperbaharui',
+ 'page_update_notification' => 'Berhasil mengupdate halaman',
'page_delete' => 'halaman dihapus',
'page_delete_notification' => 'Berhasil menghapus halaman',
'page_restore' => 'halaman telah dipulihkan',
'favourite_add_notification' => '":name" telah ditambahkan ke favorit Anda',
'favourite_remove_notification' => '":name" telah dihapus dari favorit Anda',
+ // MFA
+ 'mfa_setup_method_notification' => 'Multi-factor method successfully configured',
+ 'mfa_remove_method_notification' => 'Multi-factor method successfully removed',
+
// Other
'commented_on' => 'berkomentar pada',
'permissions_update' => 'izin diperbarui',
'user_invite_page_welcome' => 'Selamat datang di :appName!',
'user_invite_page_text' => 'Untuk menyelesaikan akun Anda dan mendapatkan akses, Anda perlu mengatur kata sandi yang akan digunakan untuk masuk ke :appName pada kunjungan berikutnya.',
'user_invite_page_confirm_button' => 'Konfirmasi Kata sandi',
- 'user_invite_success' => 'Atur kata sandi, Anda sekarang memiliki akses ke :appName!'
+ 'user_invite_success' => 'Atur kata sandi, Anda sekarang memiliki akses ke :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_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.',
];
\ No newline at end of file
return [
// Buttons
- 'cancel' => 'Batalkan',
+ 'cancel' => 'Batal',
'confirm' => 'Konfirmasi',
'back' => 'Kembali',
'save' => 'Simpan',
- 'continue' => 'Selanjutnya',
+ 'continue' => 'Lanjutkan',
'select' => 'Pilih',
- 'toggle_all' => 'Alihkan semua',
- 'more' => 'Lebih',
+ 'toggle_all' => 'Alihkan Semua',
+ 'more' => 'Lebih banyak',
// Form Labels
'name' => 'Nama',
'description' => 'Deskripsi',
- 'role' => 'Wewenang',
- 'cover_image' => 'Sampul Gambar',
+ 'role' => 'Peran',
+ 'cover_image' => 'Sampul gambar',
'cover_image_description' => 'Gambar ini harus berukuran kira-kira 440x250 piksel.',
// Actions
'actions' => 'Tindakan',
- 'view' => 'Melihat',
+ 'view' => 'Lihat',
'view_all' => 'Lihat Semua',
'create' => 'Buat',
- 'update' => 'Perbaharui',
+ 'update' => 'Perbarui',
'edit' => 'Sunting',
'sort' => 'Sortir',
'move' => 'Pindahkan',
'copy' => 'Salin',
- 'reply' => 'Balasan',
+ 'reply' => 'Balas',
'delete' => 'Hapus',
'delete_confirm' => 'Konfirmasi Penghapusan',
'search' => 'Cari',
'search_clear' => 'Hapus Pencarian',
- 'reset' => 'Setel Ulang',
+ 'reset' => 'Atur ulang',
'remove' => 'Hapus',
'add' => 'Tambah',
+ 'configure' => 'Configure',
'fullscreen' => 'Layar Penuh',
'favourite' => 'Favorit',
- 'unfavourite' => 'Tidak favorit',
- 'next' => 'Lanjut',
+ 'unfavourite' => 'Batal favorit',
+ 'next' => 'Selanjutnya',
'previous' => 'Sebelumnya',
// Sort Options
- 'sort_options' => 'Sortir Pilihan',
- 'sort_direction_toggle' => 'Urutkan Arah Toggle',
- 'sort_ascending' => 'Sortir Naik',
- 'sort_descending' => 'Urutkan Menurun',
+ 'sort_options' => 'Opsi Sortir',
+ 'sort_direction_toggle' => 'Urutkan Arah Alihan',
+ 'sort_ascending' => 'Sortir Menaik',
+ 'sort_descending' => 'Sortir Menurun',
'sort_name' => 'Nama',
'sort_default' => 'Bawaan',
- 'sort_created_at' => 'Tanggal dibuat',
- 'sort_updated_at' => 'Tanggal diperbaharui',
+ 'sort_created_at' => 'Tanggal Dibuat',
+ 'sort_updated_at' => 'Tanggal Diperbarui',
// Misc
- 'deleted_user' => 'Pengguna terhapus',
- 'no_activity' => 'Tidak ada aktifitas untuk ditampilkan',
+ 'deleted_user' => 'Pengguna yang Dihapus',
+ 'no_activity' => 'Tidak ada aktivitas untuk ditampilkan',
'no_items' => 'Tidak ada item yang tersedia',
'back_to_top' => 'Kembali ke atas',
- 'toggle_details' => 'Detail Toggle',
+ 'skip_to_main_content' => 'Lewatkan ke konten utama',
+ 'toggle_details' => 'Rincian Alihan',
'toggle_thumbnails' => 'Alihkan Gambar Mini',
- 'details' => 'Detail',
- 'grid_view' => 'Tampilan bergaris',
- 'list_view' => 'Daftar Tampilan',
- 'default' => 'Default',
+ 'details' => 'Rincian',
+ 'grid_view' => 'Tampilan Bergaris',
+ 'list_view' => 'Tampilan Daftar',
+ 'default' => 'Bawaan',
'breadcrumb' => 'Breadcrumb',
// Header
'header_menu_expand' => 'Perluas Menu Tajuk',
- 'profile_menu' => 'Profile Menu',
- 'view_profile' => 'Tampilkan profil',
+ 'profile_menu' => 'Menu Profil',
+ 'view_profile' => 'Tampilkan Profil',
'edit_profile' => 'Sunting Profil',
'dark_mode' => 'Mode Gelap',
- 'light_mode' => 'Mode Cahaya',
+ 'light_mode' => 'Mode Terang',
// Layout tabs
'tab_info' => 'Informasi',
- 'tab_info_label' => 'Tab Menampilkan Informasi Sekunder',
+ 'tab_info_label' => 'Tab: Tampilkan Informasi Sekunder',
'tab_content' => 'Konten',
- 'tab_content_label' => 'Tab Menampilkan Informasi Utama',
+ 'tab_content_label' => 'Tab: Tampilkan Informasi Utama',
// Email Content
- 'email_action_help' => 'Jika Anda mengalami masalah saat mengklik tombol ":actionText", salin dan tempel URL di bawah ini ke browser web Anda:',
- 'email_rights' => 'Seluruh hak cipta',
+ 'email_action_help' => 'Jika Anda mengalami masalah saat mengklik tombol ":actionText", salin dan tempel URL di bawah ke dalam peramban web Anda:',
+ 'email_rights' => 'Hak cipta dilindungi',
// Footer Link Options
// Not directly used but available for convenience to users.
- 'privacy_policy' => 'Rahasia pribadi',
- 'terms_of_service' => 'Persyaratan Layanan',
+ 'privacy_policy' => 'Kebijakan Privasi',
+ 'terms_of_service' => 'Ketentuan Layanan',
];
'export_html' => 'File Web Berisi',
'export_pdf' => 'Dokumen PDF',
'export_text' => 'Dokumen Teks Biasa',
+ 'export_md' => 'File Markdown',
// Permissions and restrictions
'permissions' => 'Izin',
'shelves_permissions' => 'Izin Rak Buku',
'shelves_permissions_updated' => 'Izin Rak Buku Diperbarui',
'shelves_permissions_active' => 'Izin Rak Buku Aktif',
+ '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' => 'Salin Izin ke Buku',
'shelves_copy_permissions' => 'Salin Izin',
'shelves_copy_permissions_explain' => 'Ini akan menerapkan setelan izin rak buku ini saat ini ke semua buku yang ada di dalamnya. Sebelum mengaktifkan, pastikan setiap perubahan pada izin rak buku ini telah disimpan.',
'recycle_bin' => 'Tempat Sampah',
'recycle_bin_desc' => 'Di sini Anda dapat memulihkan item yang telah dihapus atau memilih untuk menghapusnya secara permanen dari sistem. Daftar ini tidak difilter, tidak seperti daftar aktivitas serupa di sistem tempat filter izin diterapkan.',
'recycle_bin_deleted_item' => 'Item yang Dihapus',
+ 'recycle_bin_deleted_parent' => 'Induk',
'recycle_bin_deleted_by' => 'Dihapus Oleh',
'recycle_bin_deleted_at' => 'Waktu Penghapusan',
'recycle_bin_permanently_delete' => 'Hapus Permanen',
'recycle_bin_restore_list' => 'Item yang akan Dipulihkan',
'recycle_bin_restore_confirm' => 'Tindakan ini akan memulihkan item yang dihapus, termasuk semua elemen anak, ke lokasi aslinya. Jika lokasi asli telah dihapus, dan sekarang berada di keranjang sampah, item induk juga perlu dipulihkan.',
'recycle_bin_restore_deleted_parent' => 'Induk item ini juga telah dihapus. Ini akan tetap dihapus sampai induknya juga dipulihkan.',
+ 'recycle_bin_restore_parent' => 'Pulihkan Induk',
'recycle_bin_destroy_notification' => 'Total :count item dari tempat sampah.',
'recycle_bin_restore_notification' => 'Total :count item yang dipulihkan dari tempat sampah.',
'audit_table_user' => 'Pengguna',
'audit_table_event' => 'Peristiwa',
'audit_table_related' => 'Item atau Detail Terkait',
+ 'audit_table_ip' => 'IP Address',
'audit_table_date' => 'Tanggal Kegiatan',
'audit_date_from' => 'Rentang Tanggal Dari',
'audit_date_to' => 'Rentang Tanggal Sampai',
'role_details' => 'Detail Peran',
'role_name' => 'Nama peran',
'role_desc' => 'Deskripsi Singkat Peran',
+ 'role_mfa_enforced' => 'Requires Multi-Factor Authentication',
'role_external_auth_id' => 'Otentikasi Eksternal IDs',
'role_system' => 'Izin Sistem',
'role_manage_users' => 'Kelola pengguna',
'role_manage_page_templates' => 'Kelola template halaman',
'role_access_api' => 'Akses Sistem API',
'role_manage_settings' => 'Kelola setelan aplikasi',
+ 'role_export_content' => 'Export content',
'role_asset' => 'Izin Aset',
'roles_system_warning' => 'Ketahuilah bahwa akses ke salah satu dari tiga izin di atas dapat memungkinkan pengguna untuk mengubah hak mereka sendiri atau orang lain dalam sistem. Hanya tetapkan peran dengan izin ini untuk pengguna tepercaya.',
'role_asset_desc' => 'Izin ini mengontrol akses default ke aset dalam sistem. Izin pada Buku, Bab, dan Halaman akan menggantikan izin ini.',
'users_api_tokens_create' => 'Buat Token',
'users_api_tokens_expires' => 'Kedaluwarsa',
'users_api_tokens_docs' => 'Dokumentasi API',
+ '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',
// API Tokens
'user_api_token_create' => 'Buat Token API',
'it' => 'Italian',
'ja' => '日本語',
'ko' => '한국어',
+ 'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nl' => 'Nederlands',
'nb' => 'Norsk (Bokmål)',
'alpha_dash' => ':attribute hanya boleh berisi huruf, angka, tanda hubung, dan garis bawah.',
'alpha_num' => ':attribute hanya boleh berisi huruf dan angka.',
'array' => ':attribute harus berupa larik.',
+ 'backup_codes' => 'The provided code is not valid or has already been used.',
'before' => ':attribute harus tanggal sebelum :date.',
'between' => [
'numeric' => ':attribute harus di antara :min dan :max.',
],
'string' => ':attribute harus berupa string.',
'timezone' => ':attribute harus menjadi zona yang valid.',
+ 'totp' => 'The provided code is not valid or has expired.',
'unique' => ':attribute sudah diambil.',
'url' => ':attribute format tidak valid.',
'uploaded' => 'Berkas tidak dapat diunggah. Server mungkin tidak menerima berkas dengan ukuran ini.',
'favourite_add_notification' => '":name" è stato aggiunto ai tuoi preferiti',
'favourite_remove_notification' => '":name" è stato rimosso dai tuoi preferiti',
+ // MFA
+ 'mfa_setup_method_notification' => 'Metodo multi-fattore impostato con successo',
+ 'mfa_remove_method_notification' => 'Metodo multi-fattore rimosso con successo',
+
// Other
'commented_on' => 'ha commentato in',
'permissions_update' => 'autorizzazioni aggiornate',
'user_invite_page_welcome' => 'Benvenuto in :appName!',
'user_invite_page_text' => 'Per completare il tuo account e ottenere l\'accesso devi impostare una password che verrà utilizzata per accedere a :appName in futuro.',
'user_invite_page_confirm_button' => 'Conferma Password',
- 'user_invite_success' => 'Password impostata, ora hai accesso a :appName!'
+ 'user_invite_success' => 'Password impostata, ora hai accesso a :appName!',
+
+ // Multi-factor Authentication
+ 'mfa_setup' => 'Imposta Autenticazione Multi-Fattore',
+ 'mfa_setup_desc' => 'Imposta l\'autenticazione multi-fattore come misura di sicurezza aggiuntiva per il tuo account.',
+ 'mfa_setup_configured' => 'Già configurata',
+ 'mfa_setup_reconfigure' => 'Riconfigura',
+ 'mfa_setup_remove_confirmation' => 'Sei sicuro di voler rimuovere questo metodo di autenticazione multi-fattore?',
+ 'mfa_setup_action' => 'Imposta',
+ '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' => 'Metodo multi-fattore configurato, si prega di effettuare nuovamente il login utilizzando il metodo configurato.',
];
\ No newline at end of file
'reset' => 'Azzera',
'remove' => 'Rimuovi',
'add' => 'Aggiungi',
+ 'configure' => 'Configura',
'fullscreen' => 'Schermo intero',
'favourite' => 'Aggiungi ai Preferiti',
'unfavourite' => 'Rimuovi dai preferiti',
'no_activity' => 'Nessuna attività da mostrare',
'no_items' => 'Nessun elemento disponibile',
'back_to_top' => 'Torna in alto',
+ 'skip_to_main_content' => 'Passa al contenuto principale',
'toggle_details' => 'Mostra Dettagli',
'toggle_thumbnails' => 'Mostra Miniature',
'details' => 'Dettagli',
'export_html' => 'File Contenuto Web',
'export_pdf' => 'File PDF',
'export_text' => 'File di testo',
+ 'export_md' => 'File Markdown',
// Permissions and restrictions
'permissions' => 'Permessi',
'shelves_permissions' => 'Permessi Libreria',
'shelves_permissions_updated' => 'Permessi Libreria Aggiornati',
'shelves_permissions_active' => 'Permessi Attivi Libreria',
+ 'shelves_permissions_cascade_warning' => 'I permessi sugli scaffali non si estendono automaticamente ai libri contenuti. Questo avviene in quanto un libro può essere presente su più scaffali. I permessi possono comunque essere copiati ai libri contenuti usando l\'opzione qui sotto.',
'shelves_copy_permissions_to_books' => 'Copia Permessi ai Libri',
'shelves_copy_permissions' => 'Copia Permessi',
'shelves_copy_permissions_explain' => 'Verranno applicati tutti i permessi della libreria ai libri contenuti. Prima di attivarlo, assicurati che ogni permesso di questa libreria sia salvato.',
'recycle_bin' => 'Cestino',
'recycle_bin_desc' => 'Qui è possibile ripristinare gli elementi che sono stati eliminati o scegliere di rimuoverli definitivamente dal sistema. Questo elenco non è filtrato a differenza di elenchi di attività simili nel sistema in cui vengono applicati i filtri autorizzazioni.',
'recycle_bin_deleted_item' => 'Elimina Elemento',
+ 'recycle_bin_deleted_parent' => 'Superiore',
'recycle_bin_deleted_by' => 'Cancellato da',
'recycle_bin_deleted_at' => 'Orario Cancellazione',
'recycle_bin_permanently_delete' => 'Elimina Definitivamente',
'recycle_bin_restore_list' => 'Elementi da Ripristinare',
'recycle_bin_restore_confirm' => 'Questa azione ripristinerà l\'elemento eliminato, compresi gli elementi figli, nella loro posizione originale. Se la posizione originale è stata eliminata, ed è ora nel cestino, anche l\'elemento padre dovrà essere ripristinato.',
'recycle_bin_restore_deleted_parent' => 'L\'elemento padre di questo elemento è stato eliminato. Questo elemento rimarrà eliminato fino a che l\'elemento padre non sarà ripristinato.',
+ 'recycle_bin_restore_parent' => 'Ripristina Superiore',
'recycle_bin_destroy_notification' => 'Eliminati :count elementi dal cestino.',
'recycle_bin_restore_notification' => 'Ripristinati :count elementi dal cestino.',
'audit_table_user' => 'Utente',
'audit_table_event' => 'Evento',
'audit_table_related' => 'Elemento o Dettaglio correlato',
+ 'audit_table_ip' => 'Indirizzo IP',
'audit_table_date' => 'Data attività',
'audit_date_from' => 'Dalla data',
'audit_date_to' => 'Alla data',
'role_details' => 'Dettagli Ruolo',
'role_name' => 'Nome Ruolo',
'role_desc' => 'Breve Descrizione del Ruolo',
+ 'role_mfa_enforced' => 'Richiesta autenticazione multi-fattore',
'role_external_auth_id' => 'ID Autenticazione Esterna',
'role_system' => 'Permessi di Sistema',
'role_manage_users' => 'Gestire gli utenti',
'role_manage_page_templates' => 'Gestisci template pagine',
'role_access_api' => 'API sistema d\'accesso',
'role_manage_settings' => 'Gestire impostazioni app',
+ 'role_export_content' => 'Esporta contenuto',
'role_asset' => 'Permessi Entità',
'roles_system_warning' => 'Siate consapevoli che l\'accesso a uno dei tre permessi qui sopra, può consentire a un utente di modificare i propri privilegi o i privilegi di altri nel sistema. Assegna ruoli con questi permessi solo ad utenti fidati.',
'role_asset_desc' => 'Questi permessi controllano l\'accesso di default alle entità. I permessi nei Libri, Capitoli e Pagine sovrascriveranno questi.',
'users_send_invite_text' => 'Puoi scegliere di inviare a questo utente un\'email di invito che permette loro di impostare la propria password altrimenti puoi impostare la password tu stesso.',
'users_send_invite_option' => 'Invia email di invito',
'users_external_auth_id' => 'ID Autenticazioni Esterna',
- 'users_external_auth_id_desc' => 'This is the ID used to match this user when communicating with your external authentication system.',
+ 'users_external_auth_id_desc' => 'Questo è l\'ID usato per abbinare questo utente quando si comunica con il sistema di autenticazione esterno.',
'users_password_warning' => 'Riempi solo se desideri cambiare la tua password:',
'users_system_public' => 'Questo utente rappresente qualsiasi ospite che visita il sito. Non può essere usato per effettuare il login ma è assegnato automaticamente.',
'users_delete' => 'Elimina Utente',
'users_social_connected' => 'L\'account :socialAccount è stato connesso correttamente al tuo profilo.',
'users_social_disconnected' => 'L\'account :socialAccount è stato disconnesso correttamente dal tuo profilo.',
'users_api_tokens' => 'Token API',
- 'users_api_tokens_none' => 'No API tokens have been created for this user',
+ 'users_api_tokens_none' => 'Nessun token API è stato creato per questo utente',
'users_api_tokens_create' => 'Crea Token',
'users_api_tokens_expires' => 'Scade',
'users_api_tokens_docs' => 'Documentazione API',
+ 'users_mfa' => 'Autenticazione multi-fattore',
+ 'users_mfa_desc' => 'Imposta l\'autenticazione multi-fattore come misura di sicurezza aggiuntiva per il tuo account.',
+ 'users_mfa_x_methods' => ':count metodo configurato|:count metodi configurati',
+ 'users_mfa_configure' => 'Configura metodi',
// API Tokens
'user_api_token_create' => 'Crea Token API',
'user_api_token_name' => 'Nome',
- 'user_api_token_name_desc' => 'Give your token a readable name as a future reminder of its intended purpose.',
+ 'user_api_token_name_desc' => 'Assegna al tuo token un nome leggibile per ricordarne la funzionalità in futuro.',
'user_api_token_expiry' => 'Data di scadenza',
- 'user_api_token_expiry_desc' => 'Set a date at which this token expires. After this date, requests made using this token will no longer work. Leaving this field blank will set an expiry 100 years into the future.',
- 'user_api_token_create_secret_message' => 'Immediately after creating this token a "Token ID" & "Token Secret" will be generated and displayed. The secret will only be shown a single time so be sure to copy the value to somewhere safe and secure before proceeding.',
+ 'user_api_token_expiry_desc' => 'Imposta una data di scadenza per questo token. Dopo questa data, le richieste che utilizzeranno questo token non funzioneranno più. Lasciando questo campo vuoto si imposterà la scadenza tra 100 anni.',
+ 'user_api_token_create_secret_message' => 'Immediatamente dopo aver creato questo token, un "Token ID" e un "Segreto Token" saranno generati e mostrati. Il segreto verrà mostrato unicamente questa volta, assicurati, quindi, di copiare il valore in un posto sicuro prima di procedere.',
'user_api_token_create_success' => 'Token API creato correttamente',
'user_api_token_update_success' => 'Token API aggiornato correttamente',
'user_api_token' => 'Token API',
'user_api_token_id' => 'Token ID',
- 'user_api_token_id_desc' => 'This is a non-editable system generated identifier for this token which will need to be provided in API requests.',
+ 'user_api_token_id_desc' => 'Questo è un identificativo non modificabile generato dal sistema per questo token e che sarà necessario fornire per le richieste tramite API.',
'user_api_token_secret' => 'Token Segreto',
- 'user_api_token_secret_desc' => 'This is a system generated secret for this token which will need to be provided in API requests. This will only be displayed this one time so copy this value to somewhere safe and secure.',
+ 'user_api_token_secret_desc' => 'Questo è un segreto generato dal sistema per questo token che sarà necessario fornire per le richieste via API. Questo valore sarà visibile unicamente in questo momento pertanto copialo in un posto sicuro.',
'user_api_token_created' => 'Token Aggiornato :timeAgo',
'user_api_token_updated' => 'Token Aggiornato :timeAgo',
'user_api_token_delete' => 'Elimina Token',
'it' => 'Italian',
'ja' => '日本語',
'ko' => '한국어',
+ 'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nl' => 'Nederlands',
'nb' => 'Norsk (Bokmål)',
'alpha_dash' => ':attribute deve contenere solo lettere, numeri e meno.',
'alpha_num' => ':attribute deve contenere solo lettere e numeri.',
'array' => ':attribute deve essere un array.',
+ 'backup_codes' => 'Il codice fornito non è valido o è già stato utilizzato.',
'before' => ':attribute deve essere una data prima del :date.',
'between' => [
'numeric' => 'Il campo :attribute deve essere tra :min e :max.',
],
'string' => ':attribute deve essere una stringa.',
'timezone' => ':attribute deve essere una zona valida.',
+ 'totp' => 'Il codice fornito non è valido o è scaduto.',
'unique' => ':attribute è già preso.',
'url' => 'Il formato :attribute non è valido.',
'uploaded' => 'Il file non può essere caricato. Il server potrebbe non accettare file di questa dimensione.',
'favourite_add_notification' => '":name" has been added to your favourites',
'favourite_remove_notification' => '":name" has been removed from your favourites',
+ // MFA
+ 'mfa_setup_method_notification' => 'Multi-factor method successfully configured',
+ 'mfa_remove_method_notification' => 'Multi-factor method successfully removed',
+
// Other
'commented_on' => 'コメントする',
'permissions_update' => 'updated permissions',
'user_invite_page_welcome' => 'Welcome to :appName!',
'user_invite_page_text' => 'To finalise your account and gain access you need to set a password which will be used to log-in to :appName on future visits.',
'user_invite_page_confirm_button' => 'Confirm Password',
- 'user_invite_success' => 'Password set, you now have access to :appName!'
+ 'user_invite_success' => 'Password set, you now have access to :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_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.',
];
\ No newline at end of file
'reset' => 'リセット',
'remove' => '削除',
'add' => '追加',
+ 'configure' => 'Configure',
'fullscreen' => 'Fullscreen',
'favourite' => 'Favourite',
'unfavourite' => 'Unfavourite',
'no_activity' => '表示するアクティビティがありません',
'no_items' => 'アイテムはありません',
'back_to_top' => '上に戻る',
+ 'skip_to_main_content' => 'Skip to main content',
'toggle_details' => '概要の表示切替',
'toggle_thumbnails' => 'Toggle Thumbnails',
'details' => '詳細',
'export_html' => 'Webページ',
'export_pdf' => 'PDF',
'export_text' => 'テキストファイル',
+ 'export_md' => 'Markdown File',
// Permissions and restrictions
'permissions' => '権限',
'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' => 'Copy Permissions to Books',
'shelves_copy_permissions' => 'Copy Permissions',
'shelves_copy_permissions_explain' => 'This will apply the current permission settings of this bookshelf to all books contained within. Before activating, ensure any changes to the permissions of this bookshelf have been saved.',
'recycle_bin' => 'Recycle Bin',
'recycle_bin_desc' => 'Here you can restore items that have been deleted or choose to permanently remove them from the system. This list is unfiltered unlike similar activity lists in the system where permission filters are applied.',
'recycle_bin_deleted_item' => 'Deleted Item',
+ 'recycle_bin_deleted_parent' => 'Parent',
'recycle_bin_deleted_by' => 'Deleted By',
'recycle_bin_deleted_at' => 'Deletion Time',
'recycle_bin_permanently_delete' => 'Permanently Delete',
'recycle_bin_restore_list' => 'Items to be Restored',
'recycle_bin_restore_confirm' => 'This action will restore the deleted item, including any child elements, to their original location. If the original location has since been deleted, and is now in the recycle bin, the parent item will also need to be restored.',
'recycle_bin_restore_deleted_parent' => 'The parent of this item has also been deleted. These will remain deleted until that parent is also restored.',
+ 'recycle_bin_restore_parent' => 'Restore Parent',
'recycle_bin_destroy_notification' => 'Deleted :count total items from the recycle bin.',
'recycle_bin_restore_notification' => 'Restored :count total items from the recycle bin.',
'audit_table_user' => 'User',
'audit_table_event' => 'Event',
'audit_table_related' => 'Related Item or Detail',
+ 'audit_table_ip' => 'IP Address',
'audit_table_date' => 'Activity Date',
'audit_date_from' => 'Date Range From',
'audit_date_to' => 'Date Range To',
'role_details' => '概要',
'role_name' => '役割名',
'role_desc' => '役割の説明',
+ 'role_mfa_enforced' => 'Requires Multi-Factor Authentication',
'role_external_auth_id' => 'External Authentication IDs',
'role_system' => 'システム権限',
'role_manage_users' => 'ユーザ管理',
'role_manage_page_templates' => 'Manage page templates',
'role_access_api' => 'Access system API',
'role_manage_settings' => 'アプリケーション設定の管理',
+ 'role_export_content' => 'Export content',
'role_asset' => 'アセット権限',
'roles_system_warning' => 'Be aware that access to any of the above three permissions can allow a user to alter their own privileges or the privileges of others in the system. Only assign roles with these permissions to trusted users.',
'role_asset_desc' => '各アセットに対するデフォルトの権限を設定します。ここで設定した権限が優先されます。',
'users_api_tokens_create' => 'Create Token',
'users_api_tokens_expires' => 'Expires',
'users_api_tokens_docs' => 'API Documentation',
+ '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',
// API Tokens
'user_api_token_create' => 'Create API Token',
'it' => 'Italian',
'ja' => '日本語',
'ko' => '한국어',
+ 'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nl' => 'Nederlands',
'nb' => 'Norsk (Bokmål)',
'alpha_dash' => ':attributeは文字, 数値, ハイフンのみが含められます。',
'alpha_num' => ':attributeは文字と数値のみが含められます。',
'array' => ':attributeは配列である必要があります。',
+ 'backup_codes' => 'The provided code is not valid or has already been used.',
'before' => ':attributeは:date以前である必要があります。',
'between' => [
'numeric' => ':attributeは:min〜:maxである必要があります。',
],
'string' => ':attributeは文字列である必要があります。',
'timezone' => ':attributeは正しいタイムゾーンである必要があります。',
+ 'totp' => 'The provided code is not valid or has expired.',
'unique' => ':attributeは既に使用されています。',
'url' => ':attributeのフォーマットは不正です。',
'uploaded' => 'The file could not be uploaded. The server may not accept files of this size.',
'favourite_add_notification' => '":name" has been added to your favourites',
'favourite_remove_notification' => '":name" has been removed from your favourites',
+ // MFA
+ 'mfa_setup_method_notification' => 'Multi-factor method successfully configured',
+ 'mfa_remove_method_notification' => 'Multi-factor method successfully removed',
+
// Other
'commented_on' => '댓글 쓰기',
'permissions_update' => 'updated permissions',
'user_invite_page_welcome' => ':appName에 오신 것을 환영합니다!',
'user_invite_page_text' => ':appName에 로그인할 때 입력할 비밀번호를 설정하세요.',
'user_invite_page_confirm_button' => '비밀번호 확인',
- 'user_invite_success' => '암호가 설정되었고, 이제 :appName에 접근할 수 있습니다.'
+ 'user_invite_success' => '암호가 설정되었고, 이제 :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_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.',
];
\ No newline at end of file
'reset' => '리셋',
'remove' => '제거',
'add' => '추가',
+ 'configure' => 'Configure',
'fullscreen' => '전체화면',
'favourite' => 'Favourite',
'unfavourite' => 'Unfavourite',
'no_activity' => '활동 없음',
'no_items' => '항목 없음',
'back_to_top' => '맨 위로',
+ 'skip_to_main_content' => 'Skip to main content',
'toggle_details' => '내용 보기',
'toggle_thumbnails' => '섬네일 보기',
'details' => '정보',
'export_html' => 'Contained Web(.html) 파일',
'export_pdf' => 'PDF 파일',
'export_text' => 'Plain Text(.txt) 파일',
+ 'export_md' => 'Markdown File',
// Permissions and restrictions
'permissions' => '권한',
'shelves_permissions' => '서가 권한',
'shelves_permissions_updated' => '서가 권한 바꿈',
'shelves_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' => '서가의 모든 책자에 이 권한을 적용합니다. 서가의 권한을 저장했는지 확인하세요.',
'recycle_bin' => 'Recycle Bin',
'recycle_bin_desc' => 'Here you can restore items that have been deleted or choose to permanently remove them from the system. This list is unfiltered unlike similar activity lists in the system where permission filters are applied.',
'recycle_bin_deleted_item' => 'Deleted Item',
+ 'recycle_bin_deleted_parent' => 'Parent',
'recycle_bin_deleted_by' => 'Deleted By',
'recycle_bin_deleted_at' => 'Deletion Time',
'recycle_bin_permanently_delete' => 'Permanently Delete',
'recycle_bin_restore_list' => 'Items to be Restored',
'recycle_bin_restore_confirm' => 'This action will restore the deleted item, including any child elements, to their original location. If the original location has since been deleted, and is now in the recycle bin, the parent item will also need to be restored.',
'recycle_bin_restore_deleted_parent' => 'The parent of this item has also been deleted. These will remain deleted until that parent is also restored.',
+ 'recycle_bin_restore_parent' => 'Restore Parent',
'recycle_bin_destroy_notification' => 'Deleted :count total items from the recycle bin.',
'recycle_bin_restore_notification' => 'Restored :count total items from the recycle bin.',
'audit_table_user' => '사용자',
'audit_table_event' => '이벤트',
'audit_table_related' => 'Related Item or Detail',
+ 'audit_table_ip' => 'IP Address',
'audit_table_date' => '활동 날짜',
'audit_date_from' => '날짜 범위 시작',
'audit_date_to' => '날짜 범위 끝',
'role_details' => '권한 정보',
'role_name' => '권한 이름',
'role_desc' => '설명',
+ 'role_mfa_enforced' => 'Requires Multi-Factor Authentication',
'role_external_auth_id' => 'LDAP 확인',
'role_system' => '시스템 권한',
'role_manage_users' => '사용자 관리',
'role_manage_page_templates' => '템플릿 관리',
'role_access_api' => '시스템 접근 API',
'role_manage_settings' => '사이트 설정 관리',
+ 'role_export_content' => 'Export content',
'role_asset' => '권한 항목',
'roles_system_warning' => 'Be aware that access to any of the above three permissions can allow a user to alter their own privileges or the privileges of others in the system. Only assign roles with these permissions to trusted users.',
'role_asset_desc' => '책자, 챕터, 문서별 권한은 이 설정에 우선합니다.',
'users_api_tokens_create' => '토큰 만들기',
'users_api_tokens_expires' => '만료',
'users_api_tokens_docs' => 'API 설명서',
+ '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',
// API Tokens
'user_api_token_create' => 'API 토큰 만들기',
'it' => 'Italian',
'ja' => '日本語',
'ko' => '한국어',
+ 'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nl' => 'Nederlands',
'nb' => 'Norsk (Bokmål)',
'alpha_dash' => ':attribute(을)를 문자, 숫자, -, _로만 구성하세요.',
'alpha_num' => ':attribute(을)를 문자, 숫자로만 구성하세요.',
'array' => ':attribute(을)를 배열로 구성하세요.',
+ 'backup_codes' => 'The provided code is not valid or has already been used.',
'before' => ':attribute(을)를 :date 전으로 설정하세요.',
'between' => [
'numeric' => ':attribute(을)를 :min~:max(으)로 구성하세요.',
],
'string' => ':attribute(을)를 문자로 구성하세요.',
'timezone' => ':attribute(을)를 유효한 시간대로 구성하세요.',
+ 'totp' => 'The provided code is not valid or has expired.',
'unique' => ':attribute(은)는 이미 있습니다.',
'url' => ':attribute(은)는 유효하지 않은 형식입니다.',
'uploaded' => '파일 크기가 서버에서 허용하는 수치를 넘습니다.',
--- /dev/null
+<?php
+/**
+ * Activity text strings.
+ * Is used for all the text within activity logs & notifications.
+ */
+return [
+
+ // Pages
+ 'page_create' => 'sukurtas puslapis',
+ 'page_create_notification' => 'Puslapis sukurtas sėkmingai',
+ 'page_update' => 'atnaujintas puslapis',
+ 'page_update_notification' => 'Puslapis sėkmingai atnaujintas',
+ 'page_delete' => 'ištrintas puslapis',
+ 'page_delete_notification' => 'Puslapis sėkmingai ištrintas',
+ 'page_restore' => 'atkurtas puslapis',
+ 'page_restore_notification' => 'Puslapis sėkmingai atkurtas',
+ 'page_move' => 'perkeltas puslapis',
+
+ // Chapters
+ 'chapter_create' => 'sukurtas skyrius',
+ 'chapter_create_notification' => 'Skyrius sėkmingai sukurtas',
+ 'chapter_update' => 'atnaujintas skyrius',
+ 'chapter_update_notification' => 'Skyrius sekmingai atnaujintas',
+ 'chapter_delete' => 'ištrintas skyrius',
+ 'chapter_delete_notification' => 'Skyrius sėkmingai ištrintas',
+ 'chapter_move' => 'perkeltas skyrius',
+
+ // Books
+ 'book_create' => 'sukurta knyga',
+ 'book_create_notification' => 'Knyga sėkmingai sukurta',
+ 'book_update' => 'atnaujinta knyga',
+ 'book_update_notification' => 'Knyga sėkmingai atnaujinta',
+ 'book_delete' => 'ištrinta knyga',
+ 'book_delete_notification' => 'Knyga sėkmingai ištrinta',
+ 'book_sort' => 'surūšiuota knyga',
+ 'book_sort_notification' => 'Knyga sėkmingai perrūšiuota',
+
+ // Bookshelves
+ 'bookshelf_create' => 'sukurta knygų lentyna',
+ 'bookshelf_create_notification' => 'Knygų lentyna sėkmingai sukurta',
+ 'bookshelf_update' => 'atnaujinta knygų lentyna',
+ 'bookshelf_update_notification' => 'Knygų lentyna sėkmingai atnaujinta',
+ 'bookshelf_delete' => 'ištrinta knygų lentyna',
+ 'bookshelf_delete_notification' => 'Knygų lentyna sėkmingai ištrinta',
+
+ // Favourites
+ 'favourite_add_notification' => '":name" has been added to your favourites',
+ 'favourite_remove_notification' => '":name" has been removed from your favourites',
+
+ // MFA
+ 'mfa_setup_method_notification' => 'Multi-factor method successfully configured',
+ 'mfa_remove_method_notification' => 'Multi-factor method successfully removed',
+
+ // Other
+ 'commented_on' => 'pakomentavo',
+ 'permissions_update' => 'atnaujinti leidimai',
+];
--- /dev/null
+<?php
+/**
+ * Authentication Language Lines
+ * The following language lines are used during authentication for various
+ * messages that we need to display to the user.
+ */
+return [
+
+ 'failed' => 'Šie įgaliojimai neatitinka mūsų įrašų.',
+ 'throttle' => 'Per daug prisijungimo bandymų. Prašome pabandyti dar kartą po :seconds sekundžių.',
+
+ // Login & Register
+ 'sign_up' => 'Užsiregistruoti',
+ 'log_in' => 'Prisijungti',
+ 'log_in_with' => 'Prisijungti su :socialDriver',
+ 'sign_up_with' => 'Užsiregistruoti su :socialDriver',
+ 'logout' => 'Atsijungti',
+
+ 'name' => 'Pavadinimas',
+ 'username' => 'Vartotojo vardas',
+ 'email' => 'Elektroninis paštas',
+ 'password' => 'Slaptažodis',
+ 'password_confirm' => 'Patvirtinti slaptažodį',
+ 'password_hint' => 'Privalo būti daugiau nei 7 simboliai',
+ 'forgot_password' => 'Pamiršote slaptažodį?',
+ 'remember_me' => 'Prisimink mane',
+ 'ldap_email_hint' => 'Prašome įvesti elektroninį paštą, kad galėtume naudotis šia paskyra.',
+ 'create_account' => 'Sukurti paskyrą',
+ 'already_have_account' => 'Jau turite paskyrą?',
+ 'dont_have_account' => 'Neturite paskyros?',
+ 'social_login' => 'Socialinis prisijungimas',
+ 'social_registration' => 'Socialinė registracija',
+ 'social_registration_text' => 'Užsiregistruoti ir prisijungti naudojantis kita paslauga.',
+
+ 'register_thanks' => 'Ačiū, kad užsiregistravote!',
+ 'register_confirm' => 'Prašome patikrinti savo elektroninį paštą ir paspausti patvirtinimo mygtuką, kad gautumėte leidimą į :appName.',
+ 'registrations_disabled' => 'Registracijos šiuo metu negalimos',
+ 'registration_email_domain_invalid' => 'Elektroninio pašto domenas neturi prieigos prie šios programos',
+ 'register_success' => 'Ačiū už prisijungimą! Dabar jūs užsiregistravote ir prisijungėte.',
+
+
+ // Password Reset
+ 'reset_password' => 'Pakeisti slaptažodį',
+ 'reset_password_send_instructions' => 'Įveskite savo elektroninį paštą žemiau ir jums bus išsiųstas elektroninis laiškas su slaptažodžio nustatymo nuoroda.',
+ 'reset_password_send_button' => 'Atsiųsti atsatymo nuorodą',
+ 'reset_password_sent' => 'Slaptažodžio nustatymo nuoroda bus išsiųsta :email jeigu elektroninio pašto adresas bus rastas sistemoje.',
+ 'reset_password_success' => 'Jūsų slaptažodis buvo sėkmingai atnaujintas.',
+ 'email_reset_subject' => 'Atnaujinti jūsų :appName slaptažodį',
+ 'email_reset_text' => 'Šį laišką gaunate, nes mes gavome slaptažodžio atnaujinimo užklausą iš jūsų paskyros.',
+ 'email_reset_not_requested' => 'Jeigu jums nereikia slaptažodžio atnaujinimo, tolimesnių veiksmų atlikti nereikia.',
+
+
+ // Email Confirmation
+ 'email_confirm_subject' => 'Patvirtinkite savo elektroninį paštą :appName',
+ 'email_confirm_greeting' => 'Ačiū už prisijungimą prie :appName!',
+ 'email_confirm_text' => 'Prašome patvirtinti savo elektroninio pašto adresą paspaudus mygtuką žemiau:',
+ 'email_confirm_action' => 'Patvirtinkite elektroninį paštą',
+ 'email_confirm_send_error' => 'Būtinas elektroninio laiško patviritnimas, bet sistema negali išsiųsti laiško. Susisiekite su administratoriumi, kad užtikrintumėte, jog elektroninis paštas atsinaujino teisingai.',
+ 'email_confirm_success' => 'Jūsų elektroninis paštas buvo patvirtintas!',
+ 'email_confirm_resent' => 'Elektroninio pašto patvirtinimas persiųstas, prašome patikrinti pašto dėžutę.',
+
+ 'email_not_confirmed' => 'Elektroninis paštas nepatvirtintas',
+ 'email_not_confirmed_text' => 'Jūsų elektroninis paštas dar vis nepatvirtintas.',
+ 'email_not_confirmed_click_link' => 'Prašome paspausti nuorodą elektroniniame pašte, kuri buvo išsiųsta iš karto po registracijos.',
+ 'email_not_confirmed_resend' => 'Jeigu nerandate elektroninio laiško, galite dar kartą išsiųsti patvirtinimo elektroninį laišką, pateikdami žemiau esančią formą.',
+ 'email_not_confirmed_resend_button' => 'Persiųsti patvirtinimo laišką',
+
+ // User Invite
+ 'user_invite_email_subject' => 'Jūs buvote pakviestas prisijungti prie :appName!',
+ 'user_invite_email_greeting' => 'Paskyra buvo sukurta jums :appName.',
+ 'user_invite_email_text' => 'Paspauskite mygtuką žemiau, kad sukurtumėte paskyros slaptažodį ir gautumėte prieigą:',
+ 'user_invite_email_action' => 'Sukurti paskyros slaptažodį',
+ 'user_invite_page_welcome' => 'Sveiki atvykę į :appName!',
+ 'user_invite_page_text' => 'Norėdami galutinai pabaigti paskyrą ir gauti prieigą jums reikia nustatyti slaptažodį, kuris bus naudojamas prisijungiant prie :appName ateities vizitų metu.',
+ 'user_invite_page_confirm_button' => 'Patvirtinti slaptažodį',
+ 'user_invite_success' => 'Slaptažodis nustatytas, dabar turite prieigą prie :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_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.',
+];
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * Common elements found throughout many areas of BookStack.
+ */
+return [
+
+ // Buttons
+ 'cancel' => 'Atšaukti',
+ 'confirm' => 'Patvirtinti',
+ 'back' => 'Grįžti',
+ 'save' => 'Išsaugoti',
+ 'continue' => 'Praleisti',
+ 'select' => 'Pasirinkti',
+ 'toggle_all' => 'Perjungti visus',
+ 'more' => 'Daugiau',
+
+ // Form Labels
+ 'name' => 'Pavadinimas',
+ 'description' => 'Apibūdinimas',
+ 'role' => 'Vaidmuo',
+ 'cover_image' => 'Viršelio nuotrauka',
+ 'cover_image_description' => 'Ši nuotrauka turi būti maždaug 440x250px.',
+
+ // Actions
+ 'actions' => 'Veiksmai',
+ 'view' => 'Rodyti',
+ 'view_all' => 'Rodyti visus',
+ 'create' => 'Sukurti',
+ 'update' => 'Atnaujinti',
+ 'edit' => 'Redaguoti',
+ 'sort' => 'Rūšiuoti',
+ 'move' => 'Perkelti',
+ 'copy' => 'Kopijuoti',
+ 'reply' => 'Atsakyti',
+ 'delete' => 'Ištrinti',
+ 'delete_confirm' => 'Patvirtinti ištrynimą',
+ 'search' => 'Paieška',
+ 'search_clear' => 'Išvalyti paiešką',
+ 'reset' => 'Atsatyti',
+ 'remove' => 'Pašalinti',
+ 'add' => 'Pridėti',
+ 'configure' => 'Configure',
+ 'fullscreen' => 'Visas ekranas',
+ 'favourite' => 'Favourite',
+ 'unfavourite' => 'Unfavourite',
+ 'next' => 'Next',
+ 'previous' => 'Previous',
+
+ // Sort Options
+ 'sort_options' => 'Rūšiuoti pasirinkimus',
+ 'sort_direction_toggle' => 'Rūšiuoti krypties perjungimus',
+ 'sort_ascending' => 'Rūšiuoti didėjančia tvarka',
+ 'sort_descending' => 'Rūšiuoti mažėjančia tvarka',
+ 'sort_name' => 'Pavadinimas',
+ 'sort_default' => 'Numatytas',
+ 'sort_created_at' => 'Sukurta data',
+ 'sort_updated_at' => 'Atnaujinta data',
+
+ // Misc
+ 'deleted_user' => 'Ištrinti naudotoją',
+ 'no_activity' => 'Nėra veiklų',
+ 'no_items' => 'Nėra elementų',
+ 'back_to_top' => 'Grįžti į pradžią',
+ 'skip_to_main_content' => 'Skip to main content',
+ 'toggle_details' => 'Perjungti detales',
+ 'toggle_thumbnails' => 'Perjungti miniatūras',
+ 'details' => 'Detalės',
+ 'grid_view' => 'Tinklelio vaizdas',
+ 'list_view' => 'Sąrašas',
+ 'default' => 'Numatytas',
+ 'breadcrumb' => 'Duonos rėžis',
+
+ // Header
+ 'header_menu_expand' => 'Plėsti antraštės meniu',
+ 'profile_menu' => 'Profilio meniu',
+ 'view_profile' => 'Rodyti porofilį',
+ 'edit_profile' => 'Redaguoti profilį',
+ 'dark_mode' => 'Tamsus rėžimas',
+ 'light_mode' => 'Šviesus rėžimas',
+
+ // Layout tabs
+ 'tab_info' => 'Informacija',
+ 'tab_info_label' => 'Skirtukas: Rodyti antrinę informaciją',
+ 'tab_content' => 'Turinys',
+ 'tab_content_label' => 'Skirtukas: Rodyti pirminę informaciją',
+
+ // Email Content
+ 'email_action_help' => 'Jeigu kyla problemų spaudžiant :actionText: mygtuką, nukopijuokite ir įklijuokite URL į savo naršyklę.',
+ 'email_rights' => 'Visos teisės rezervuotos',
+
+ // Footer Link Options
+ // Not directly used but available for convenience to users.
+ 'privacy_policy' => 'Privatumo politika',
+ 'terms_of_service' => 'Paslaugų teikimo paslaugos',
+];
--- /dev/null
+<?php
+/**
+ * Text used in custom JavaScript driven components.
+ */
+return [
+
+ // Image Manager
+ 'image_select' => 'Nuotraukų pasirinkimas',
+ 'image_all' => 'Visi',
+ 'image_all_title' => 'Rodyti visas nuotraukas',
+ 'image_book_title' => 'Peržiūrėti nuotraukas, įkeltas į šią knygą',
+ 'image_page_title' => 'Peržiūrėti nuotraukas, įkeltas į šį puslapį',
+ 'image_search_hint' => 'Ieškoti pagal nuotraukos pavadinimą',
+ 'image_uploaded' => 'Įkelta :uploadedDate',
+ 'image_load_more' => 'Rodyti daugiau',
+ 'image_image_name' => 'Nuotraukos pavadinimas',
+ 'image_delete_used' => 'Ši nuotrauka yra naudojama puslapyje žemiau.',
+ 'image_delete_confirm_text' => 'Ar jūs esate tikri, kad norite ištrinti šią nuotrauką?',
+ 'image_select_image' => 'Pasirinkti nuotrauką',
+ 'image_dropzone' => 'Tempkite nuotraukas arba spauskite šia, kad įkeltumėte',
+ 'images_deleted' => 'Nuotraukos ištrintos',
+ 'image_preview' => 'Nuotraukų peržiūra',
+ 'image_upload_success' => 'Nuotrauka įkelta sėkmingai',
+ 'image_update_success' => 'Nuotraukos detalės sėkmingai atnaujintos',
+ 'image_delete_success' => 'Nuotrauka sėkmingai ištrinti',
+ 'image_upload_remove' => 'Pašalinti',
+
+ // Code Editor
+ 'code_editor' => 'Redaguoti kodą',
+ 'code_language' => 'Kodo kalba',
+ 'code_content' => 'Kodo turinys',
+ 'code_session_history' => 'Sesijos istorija',
+ 'code_save' => 'Išsaugoti kodą',
+];
--- /dev/null
+<?php
+/**
+ * Text used for 'Entities' (Document Structure Elements) such as
+ * Books, Shelves, Chapters & Pages
+ */
+return [
+
+ // Shared
+ 'recently_created' => 'Neseniai sukurtas',
+ 'recently_created_pages' => 'Neseniai sukurti puslapiai',
+ 'recently_updated_pages' => 'Neseniai atnaujinti puslapiai',
+ 'recently_created_chapters' => 'Neseniai sukurti skyriai',
+ 'recently_created_books' => 'Neseniai sukurtos knygos',
+ 'recently_created_shelves' => 'Neseniai sukurtos lentynos',
+ 'recently_update' => 'Neseniai atnaujinta',
+ 'recently_viewed' => 'Neseniai peržiūrėta',
+ 'recent_activity' => 'Paskutiniai veiksmai',
+ 'create_now' => 'Sukurti vieną dabar',
+ 'revisions' => 'Pataisymai',
+ 'meta_revision' => 'Pataisymas #:revisionCount',
+ 'meta_created' => 'Sukurta :timeLength',
+ 'meta_created_name' => 'Sukurta :timeLength naudotojo :user',
+ 'meta_updated' => 'Atnaujintas :timeLength',
+ 'meta_updated_name' => 'Atnaujinta :timeLength naudotojo :user',
+ 'meta_owned_name' => 'Priklauso :user',
+ 'entity_select' => 'Pasirinkti subjektą',
+ 'images' => 'Nuotraukos',
+ 'my_recent_drafts' => 'Naujausi išsaugoti juodraščiai',
+ 'my_recently_viewed' => 'Neseniai peržiūrėti',
+ 'my_most_viewed_favourites' => 'My Most Viewed Favourites',
+ 'my_favourites' => 'My Favourites',
+ 'no_pages_viewed' => 'Jūs neperžiūrėjote nei vieno puslapio',
+ 'no_pages_recently_created' => 'Nebuvos sukurta jokių puslapių',
+ 'no_pages_recently_updated' => 'Nebuvo atnaujinta jokių puslapių',
+ 'export' => 'Eksportuoti',
+ 'export_html' => 'Sudėtinis žiniatinklio failas',
+ 'export_pdf' => 'PDF failas',
+ 'export_text' => 'Paprastas failo tekstas',
+ 'export_md' => 'Markdown File',
+
+ // Permissions and restrictions
+ 'permissions' => 'Leidimai',
+ 'permissions_intro' => 'Įgalinus šias teises, pirmenybė bus teikiama visiems nustatytiems vaidmenų leidimams.',
+ 'permissions_enable' => 'Įgalinti pasirinktus leidimus',
+ 'permissions_save' => 'Išsaugoti leidimus',
+ 'permissions_owner' => 'Savininkas',
+
+ // Search
+ 'search_results' => 'Ieškoti rezultatų',
+ 'search_total_results_found' => ':count rastas rezultatas|:count iš viso rezultatų rasta',
+ 'search_clear' => 'Išvalyti paiešką',
+ 'search_no_pages' => 'Nėra puslapių pagal šią paiešką',
+ 'search_for_term' => 'Ieškoti pagal :term',
+ 'search_more' => 'Daugiau rezultatų',
+ 'search_advanced' => 'Išplėstinė paieška',
+ 'search_terms' => 'Ieškoti terminų',
+ 'search_content_type' => 'Turinio tipas',
+ 'search_exact_matches' => 'Tikslūs atitikmenys',
+ 'search_tags' => 'Žymių paieškos',
+ 'search_options' => 'Parinktys',
+ 'search_viewed_by_me' => 'Mano peržiūrėta',
+ 'search_not_viewed_by_me' => 'Mano neperžiūrėta',
+ 'search_permissions_set' => 'Nustatyti leidimus',
+ 'search_created_by_me' => 'Mano sukurta',
+ 'search_updated_by_me' => 'Mano atnaujinimas',
+ 'search_owned_by_me' => 'Priklauso man',
+ 'search_date_options' => 'Datos parinktys',
+ 'search_updated_before' => 'Atnaujinta prieš',
+ 'search_updated_after' => 'Atnaujinta po',
+ 'search_created_before' => 'Sukurta prieš',
+ 'search_created_after' => 'Sukurta po',
+ 'search_set_date' => 'Nustatyti datą',
+ 'search_update' => 'Atnaujinti paiešką',
+
+ // Shelves
+ 'shelf' => 'Lentyna',
+ 'shelves' => 'Lentynos',
+ 'x_shelves' => ':count lentyna|:count lentynos',
+ 'shelves_long' => 'Knygų lentynos',
+ 'shelves_empty' => 'Nebuvo sukurtos jokios lentynos',
+ 'shelves_create' => 'Sukurti naują lentyną',
+ 'shelves_popular' => 'Populiarios lentynos',
+ 'shelves_new' => 'Naujos lentynos',
+ 'shelves_new_action' => 'Nauja lentyna',
+ 'shelves_popular_empty' => 'Populiariausios knygos pasirodys čia.',
+ 'shelves_new_empty' => 'Visai neseniai sukurtos lentynos pasirodys čia.',
+ 'shelves_save' => 'Išsaugoti lenyną',
+ 'shelves_books' => 'Knygos šioje lentynoje',
+ 'shelves_add_books' => 'Pridėti knygas į šią lentyną',
+ 'shelves_drag_books' => 'Vilkite knygas čia, kad pridėtumėte jas į šią lentyną',
+ 'shelves_empty_contents' => 'Ši lentyną neturi jokių pridėtų knygų',
+ 'shelves_edit_and_assign' => 'Redaguoti lentyną, kad pridėti knygų',
+ 'shelves_edit_named' => 'Redaguoti knygų lentyną :name',
+ 'shelves_edit' => 'Redaguoti knygų lentyną',
+ 'shelves_delete' => 'Ištrinti knygų lentyną',
+ 'shelves_delete_named' => 'Ištrinti knygų lentyną :name',
+ 'shelves_delete_explain' => "This will delete the bookshelf with the name ':name'. Contained books will not be deleted.",
+ 'shelves_delete_confirmation' => 'Ar jūs esate tikri, kad norite ištrinti šią knygų lentyną?',
+ 'shelves_permissions' => 'Knygų lentynos leidimai',
+ 'shelves_permissions_updated' => 'Knygų lentynos leidimai atnaujinti',
+ 'shelves_permissions_active' => 'Knygų lentynos leidimai aktyvūs',
+ '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' => 'Kopijuoti leidimus knygoms',
+ 'shelves_copy_permissions' => 'Kopijuoti leidimus',
+ 'shelves_copy_permissions_explain' => 'Visoms knygoms, esančioms šioje knygų lentynoje, bus taikomi dabartiniai leidimų nustatymai. Prieš suaktyvindami įsitikinkite, kad visi šios knygų lentynos leidimų pakeitimai buvo išsaugoti.',
+ 'shelves_copy_permission_success' => 'Knygų lentynos leidimai nukopijuoti į :count knygas',
+
+ // Books
+ 'book' => 'Knyga',
+ 'books' => 'Knygos',
+ 'x_books' => ':count knyga|:count knygos',
+ 'books_empty' => 'Nebuvo sukurta jokių knygų',
+ 'books_popular' => 'Populiarios knygos',
+ 'books_recent' => 'Naujos knygos',
+ 'books_new' => 'Naujos knygos',
+ 'books_new_action' => 'Nauja knyga',
+ 'books_popular_empty' => 'Čia pasirodys pačios populiariausios knygos.',
+ 'books_new_empty' => 'Čia pasirodys naujausios sukurtos knygos',
+ 'books_create' => 'Sukurti naują knygą',
+ 'books_delete' => 'Ištrinti knygą',
+ 'books_delete_named' => 'Ištrinti knygą :bookName',
+ 'books_delete_explain' => 'Tai ištrins knygą su pavadinimu \':bookName\'. Visi puslapiai ir skyriai bus pašalinti.',
+ 'books_delete_confirmation' => 'Ar jūs esate tikri, kad norite ištrinti šią knygą?',
+ 'books_edit' => 'Redaguoti knygą',
+ 'books_edit_named' => 'Redaguoti knygą :bookName',
+ 'books_form_book_name' => 'Knygos pavadinimas',
+ 'books_save' => 'Išsaugoti knygą',
+ 'books_permissions' => 'Knygos leidimas',
+ 'books_permissions_updated' => 'Knygos leidimas atnaujintas',
+ 'books_empty_contents' => 'Jokių puslapių ar skyrių nebuvo skurta šiai knygai',
+ 'books_empty_create_page' => 'Sukurti naują puslapį',
+ 'books_empty_sort_current_book' => 'Rūšiuoti dabartinę knygą',
+ 'books_empty_add_chapter' => 'Pridėti skyrių',
+ 'books_permissions_active' => 'Knygos leidimas aktyvus',
+ 'books_search_this' => 'Ieškoti šioje knygoje',
+ 'books_navigation' => 'Knygos naršymas',
+ 'books_sort' => 'Rūšiuoti pagal knygos turinį',
+ 'books_sort_named' => 'Rūšiuoti knygą :bookName',
+ 'books_sort_name' => 'Rūšiuoti pagal vardą',
+ 'books_sort_created' => 'Rūšiuoti pagal sukūrimo datą',
+ 'books_sort_updated' => 'Rūšiuoti pagal atnaujinimo datą',
+ 'books_sort_chapters_first' => 'Skyriaus pradžia',
+ 'books_sort_chapters_last' => 'Skyriaus pabaiga',
+ 'books_sort_show_other' => 'Rodyti kitas knygas',
+ 'books_sort_save' => 'Išsaugoti naują įsakymą',
+
+ // Chapters
+ 'chapter' => 'Skyrius',
+ 'chapters' => 'Skyriai',
+ 'x_chapters' => ':count skyrius|:count skyriai',
+ 'chapters_popular' => 'Populiarūs skyriai',
+ 'chapters_new' => 'Naujas skyrius',
+ 'chapters_create' => 'Sukurti naują skyrių',
+ 'chapters_delete' => 'Ištrinti skyrių',
+ 'chapters_delete_named' => 'Ištrinti skyrių :chapterName',
+ 'chapters_delete_explain' => 'Tai ištrins skyrių su pavadinimu \':chapterName\. Visi puslapiai, esantys šiame skyriuje, taip pat bus ištrinti.',
+ 'chapters_delete_confirm' => 'Ar esate tikri, jog norite ištrinti šį skyrių?',
+ 'chapters_edit' => 'Redaguoti skyrių',
+ 'chapters_edit_named' => 'Redaguoti skyrių :chapterName',
+ 'chapters_save' => 'Išsaugoti skyrių',
+ 'chapters_move' => 'Perkelti skyrių',
+ 'chapters_move_named' => 'Perkelti skyrių :chapterName',
+ 'chapter_move_success' => 'Skyrius perkeltas į :bookName',
+ 'chapters_permissions' => 'Skyriaus leidimai',
+ 'chapters_empty' => 'Šiuo metu skyriuje nėra puslapių',
+ 'chapters_permissions_active' => 'Skyriaus leidimai aktyvūs',
+ 'chapters_permissions_success' => 'Skyriaus leidimai atnaujinti',
+ 'chapters_search_this' => 'Ieškoti šio skyriaus',
+
+ // Pages
+ 'page' => 'Puslapis',
+ 'pages' => 'Puslapiai',
+ 'x_pages' => ':count puslapis|:count puslapiai',
+ 'pages_popular' => 'Populiarūs puslapiai',
+ 'pages_new' => 'Naujas puslapis',
+ 'pages_attachments' => 'Priedai',
+ 'pages_navigation' => 'Puslapių navigacija',
+ 'pages_delete' => 'Ištrinti puslapį',
+ 'pages_delete_named' => 'Ištrinti puslapį :pageName',
+ 'pages_delete_draft_named' => 'Ištrinti juodraščio puslapį :pageName',
+ 'pages_delete_draft' => 'Ištrinti juodraščio puslapį',
+ 'pages_delete_success' => 'Puslapis ištrintas',
+ 'pages_delete_draft_success' => 'Juodraščio puslapis ištrintas',
+ 'pages_delete_confirm' => 'Ar esate tikri, kad norite ištrinti šį puslapį?',
+ 'pages_delete_draft_confirm' => 'Ar esate tikri, kad norite ištrinti šį juodraščio puslapį?',
+ 'pages_editing_named' => 'Redaguojamas puslapis :pageName',
+ 'pages_edit_draft_options' => 'Juodrasčio pasirinkimai',
+ 'pages_edit_save_draft' => 'Išsaugoti juodraštį',
+ 'pages_edit_draft' => 'Redaguoti juodraščio puslapį',
+ 'pages_editing_draft' => 'Redaguojamas juodraštis',
+ 'pages_editing_page' => 'Redaguojamas puslapis',
+ 'pages_edit_draft_save_at' => 'Juodraštis išsaugotas',
+ 'pages_edit_delete_draft' => 'Ištrinti juodraštį',
+ 'pages_edit_discard_draft' => 'Išmesti juodraštį',
+ 'pages_edit_set_changelog' => 'Nustatyti keitimo žurnalą',
+ 'pages_edit_enter_changelog_desc' => 'Įveskite trumpus, jūsų atliktus, pokyčių aprašymus',
+ 'pages_edit_enter_changelog' => 'Įeiti į keitimo žurnalą',
+ 'pages_save' => 'Išsaugoti puslapį',
+ 'pages_title' => 'Puslapio antraštė',
+ 'pages_name' => 'Puslapio pavadinimas',
+ 'pages_md_editor' => 'Redaguotojas',
+ 'pages_md_preview' => 'Peržiūra',
+ 'pages_md_insert_image' => 'Įterpti nuotrauką',
+ 'pages_md_insert_link' => 'Įterpti subjekto nuorodą',
+ 'pages_md_insert_drawing' => 'Įterpti piešinį',
+ 'pages_not_in_chapter' => 'Puslapio nėra skyriuje',
+ 'pages_move' => 'Perkelti puslapį',
+ 'pages_move_success' => 'Puslapis perkeltas į ":parentName"',
+ 'pages_copy' => 'Nukopijuoti puslapį',
+ 'pages_copy_desination' => 'Nukopijuoti tikslą',
+ 'pages_copy_success' => 'Puslapis sėkmingai nukopijuotas',
+ 'pages_permissions' => 'Puslapio leidimai',
+ 'pages_permissions_success' => 'Puslapio leidimai atnaujinti',
+ 'pages_revision' => 'Peržiūra',
+ 'pages_revisions' => 'Puslapio peržiūros',
+ 'pages_revisions_named' => 'Peržiūros puslapio :pageName',
+ 'pages_revision_named' => 'Peržiūra puslapio :pageName',
+ 'pages_revision_restored_from' => 'Atkurta iš #:id; :summary',
+ 'pages_revisions_created_by' => 'Sukurta',
+ 'pages_revisions_date' => 'Peržiūros data',
+ 'pages_revisions_number' => '#',
+ 'pages_revisions_numbered' => 'Peržiūra #:id',
+ 'pages_revisions_numbered_changes' => 'Peržiūros #:id pokyčiai',
+ 'pages_revisions_changelog' => 'Keitimo žurnalas',
+ 'pages_revisions_changes' => 'Pakeitimai',
+ 'pages_revisions_current' => 'Dabartinė versija',
+ 'pages_revisions_preview' => 'Peržiūra',
+ 'pages_revisions_restore' => 'Atkurti',
+ 'pages_revisions_none' => 'Šis puslapis neturi peržiūrų',
+ 'pages_copy_link' => 'Kopijuoti nuorodą',
+ 'pages_edit_content_link' => 'Redaguoti turinį',
+ 'pages_permissions_active' => 'Puslapio leidimai aktyvūs',
+ 'pages_initial_revision' => 'Pradinis skelbimas',
+ 'pages_initial_name' => 'Naujas puslapis',
+ 'pages_editing_draft_notification' => 'Dabar jūs redaguojate juodraštį, kuris paskutinį kartą buvo išsaugotas :timeDiff',
+ 'pages_draft_edited_notification' => 'Šis puslapis buvo redaguotas iki to laiko. Rekomenduojame jums išmesti šį juodraštį.',
+ 'pages_draft_edit_active' => [
+ 'start_a' => ':count naudotojai pradėjo redaguoti šį puslapį',
+ 'start_b' => ':userName pradėjo redaguoti šį puslapį',
+ 'time_a' => 'nuo puslapio paskutinio atnaujinimo',
+ 'time_b' => 'paskutinėmis :minCount minutėmis',
+ 'message' => ':start :time. Pasistenkite neperrašyti vienas kito atnaujinimų!',
+ ],
+ 'pages_draft_discarded' => 'Juodraštis atmestas, redaguotojas atnaujintas dabartinis puslapio turinys',
+ 'pages_specific' => 'Specifinis puslapis',
+ 'pages_is_template' => 'Puslapio šablonas',
+
+ // Editor Sidebar
+ 'page_tags' => 'Puslapio žymos',
+ 'chapter_tags' => 'Skyriaus žymos',
+ 'book_tags' => 'Knygos žymos',
+ 'shelf_tags' => 'Lentynų žymos',
+ 'tag' => 'Žymos',
+ 'tags' => 'Tags',
+ 'tag_name' => 'Tag Name',
+ 'tag_value' => 'Žymos vertė (neprivaloma)',
+ 'tags_explain' => "Add some tags to better categorise your content. \n You can assign a value to a tag for more in-depth organisation.",
+ 'tags_add' => 'Pridėti kitą žymą',
+ 'tags_remove' => 'Pridėti kitą žymą',
+ 'attachments' => 'Priedai',
+ 'attachments_explain' => 'Įkelkite kelis failus arba pridėkite nuorodas savo puslapyje. Jie matomi puslapio šoninėje juostoje.',
+ 'attachments_explain_instant_save' => 'Pakeitimai čia yra išsaugomi akimirksniu.',
+ 'attachments_items' => 'Pridėti elementai',
+ 'attachments_upload' => 'Įkelti failą',
+ 'attachments_link' => 'Pridėti nuorodą',
+ 'attachments_set_link' => 'Nustatyti nuorodą',
+ 'attachments_delete' => 'Ar esate tikri, kad norite ištrinti šį priedą?',
+ 'attachments_dropzone' => 'Numesti failus arba paspausti čia ir pridėti failą',
+ 'attachments_no_files' => 'Failai nebuvo įkelti',
+ 'attachments_explain_link' => 'Jūs galite pridėti nuorodas, jei nenorite įkelti failo. Tai gali būti nuoroda į kitą puslapį arba nuoroda į failą debesyje.',
+ 'attachments_link_name' => 'Nuorodos pavadinimas',
+ 'attachment_link' => 'Priedo nuoroda',
+ 'attachments_link_url' => 'Nuoroda į failą',
+ 'attachments_link_url_hint' => 'URL į failą',
+ 'attach' => 'Pridėti',
+ 'attachments_insert_link' => 'Pridėti priedo nuorodą į puslapį',
+ 'attachments_edit_file' => 'Redaguoti failą',
+ 'attachments_edit_file_name' => 'Failo pavadinimas',
+ 'attachments_edit_drop_upload' => 'Numesti failus arba spausti čia ir atsisiųsti ir perrašyti',
+ 'attachments_order_updated' => 'Atnaujintas priedų išsidėstymas',
+ 'attachments_updated_success' => 'Priedų detalės atnaujintos',
+ 'attachments_deleted' => 'Priedas ištrintas',
+ 'attachments_file_uploaded' => 'Failas sėkmingai įkeltas',
+ 'attachments_file_updated' => 'Failas sėkmingai atnaujintas',
+ 'attachments_link_attached' => 'Nuoroda sėkmingai pridėta puslapyje',
+ 'templates' => 'Šablonai',
+ 'templates_set_as_template' => 'Puslapis yra šablonas',
+ 'templates_explain_set_as_template' => 'Jūs galite nustatyti šį puslapį kaip šabloną, jo turinys bus panaudotas, kuriant kitus puslapius. Kiti naudotojai galės naudotis šiuo šablonu, jei turės peržiūros leidimą šiam puslapiui.',
+ 'templates_replace_content' => 'Pakeisti puslapio turinį',
+ 'templates_append_content' => 'Papildyti puslapio turinį',
+ 'templates_prepend_content' => 'Priklauso nuo puslapio turinio',
+
+ // Profile View
+ 'profile_user_for_x' => 'Naudotojas :time',
+ 'profile_created_content' => 'Sukurtas tyrinys',
+ 'profile_not_created_pages' => ':userName nesukūrė jokio puslapio',
+ 'profile_not_created_chapters' => ':userName nesukūrė jokio skyriaus',
+ 'profile_not_created_books' => ':userName nesukūrė jokios knygos',
+ 'profile_not_created_shelves' => ':userName nesukūrė jokių lentynų',
+
+ // Comments
+ 'comment' => 'Komentaras',
+ 'comments' => 'Komentarai',
+ 'comment_add' => 'Pridėti komentarą',
+ 'comment_placeholder' => 'Palikite komentarą čia',
+ 'comment_count' => '{0} Nėra komentarų|{1} 1 komentaras|[2,*] :count komentarai',
+ 'comment_save' => 'Išsaugoti komentarą',
+ 'comment_saving' => 'Komentaras išsaugojamas...',
+ 'comment_deleting' => 'Komentaras ištrinamas...',
+ 'comment_new' => 'Naujas komentaras',
+ 'comment_created' => 'Pakomentuota :createDiff',
+ 'comment_updated' => 'Atnaujinta :updateDiff pagal :username',
+ 'comment_deleted_success' => 'Komentaras ištrintas',
+ 'comment_created_success' => 'Komentaras pridėtas',
+ 'comment_updated_success' => 'Komentaras atnaujintas',
+ 'comment_delete_confirm' => 'Esate tikri, kad norite ištrinti šį komentarą?',
+ 'comment_in_reply_to' => 'Atsakydamas į :commentId',
+
+ // Revision
+ 'revision_delete_confirm' => 'Esate tikri, kad norite ištrinti šią peržiūrą?',
+ 'revision_restore_confirm' => 'Esate tikri, kad norite atkurti šią peržiūrą? Dabartinis puslapio turinys bus pakeistas.',
+ 'revision_delete_success' => 'Peržiūra ištrinta',
+ 'revision_cannot_delete_latest' => 'Negalima išrinti vėliausios peržiūros'
+];
--- /dev/null
+<?php
+/**
+ * Text shown in error messaging.
+ */
+return [
+
+ // Permissions
+ 'permission' => 'Jūs neturite leidimo atidaryti šio puslapio.',
+ 'permissionJson' => 'Jūs neturite leidimo atlikti prašomo veiksmo.',
+
+ // Auth
+ 'error_user_exists_different_creds' => 'Naudotojo elektroninis paštas :email jau egzistuoja, bet su kitokiais įgaliojimais.',
+ 'email_already_confirmed' => 'Elektroninis paštas jau buvo patvirtintas, pabandykite prisijungti.',
+ 'email_confirmation_invalid' => 'Šis patvirtinimo prieigos raktas negalioja arba jau buvo panaudotas, prašome bandykite vėl registruotis.',
+ 'email_confirmation_expired' => 'Šis patvirtinimo prieigos raktas baigė galioti, naujas patvirtinimo laiškas jau išsiųstas elektroniniu paštu.',
+ 'email_confirmation_awaiting' => 'Elektroninio pašto adresą paskyrai reikia patvirtinti',
+ 'ldap_fail_anonymous' => 'Nepavyko pasiekti LDAP naudojant anoniminį susiejimą',
+ 'ldap_fail_authed' => 'Nepavyko pasiekti LDAP naudojant išsamią dn ir slaptažodžio informaciją',
+ 'ldap_extension_not_installed' => 'LDAP PHP išplėtimas neįdiegtas',
+ 'ldap_cannot_connect' => 'Negalima prisijungti prie LDAP serverio, nepavyko prisijungti',
+ 'saml_already_logged_in' => 'Jau prisijungta',
+ 'saml_user_not_registered' => 'Naudotojas :name neužregistruotas ir automatinė registracija yra išjungta',
+ 'saml_no_email_address' => 'Nerandamas šio naudotojo elektroninio pašto adresas išorinės autentifikavimo sistemos pateiktuose duomenyse',
+ 'saml_invalid_response_id' => 'Prašymas iš išorinės autentifikavimo sistemos nėra atpažintas proceso, kurį pradėjo ši programa. Naršymas po prisijungimo gali sukelti šią problemą.',
+ 'saml_fail_authed' => 'Prisijungimas, naudojant :system nepavyko, sistema nepateikė sėkmingo leidimo.',
+ 'social_no_action_defined' => 'Neapibrėžtas joks veiksmas',
+ 'social_login_bad_response' => "Error received during :socialAccount login: \n:error",
+ 'social_account_in_use' => 'Ši :socialAccount paskyra jau yra naudojama, pabandykite prisijungti per :socialAccount pasirinkimą.',
+ 'social_account_email_in_use' => 'Elektroninis paštas :email jau yra naudojamas. Jei jūs jau turite paskyrą, galite prijungti savo :socialAccount paskyrą iš savo profilio nustatymų.',
+ 'social_account_existing' => 'Šis :socialAccount jau yra pridėtas prie jūsų profilio.',
+ 'social_account_already_used_existing' => 'Ši :socialAccount paskyra jau yra naudojama kito naudotojo.',
+ 'social_account_not_used' => 'Ši :socialAccount paskyra nėra susieta su jokiais naudotojais. Prašome, pridėkite ją į savo profilio nustatymus.',
+ 'social_account_register_instructions' => 'Jei dar neturite paskyros, galite užregistruoti paskyrą, naudojant :socialAccount pasirinkimą.',
+ 'social_driver_not_found' => 'Socialinis diskas nerastas',
+ 'social_driver_not_configured' => 'Jūsų :socialAccount socaliniai nustatymai sukonfigūruoti neteisingai.',
+ 'invite_token_expired' => 'Ši kvietimo nuoroda baigė galioti. Vietoj to, jūs galite bandyti iš naujo nustatyti savo paskyros slaptažodį.',
+
+ // System
+ 'path_not_writable' => 'Į failo kelią :filePath negalima įkelti. Įsitikinkite, kad jis yra įrašomas į serverį.',
+ 'cannot_get_image_from_url' => 'Negalima gauti vaizdo iš :url',
+ 'cannot_create_thumbs' => 'Serveris negali sukurti miniatiūros. Prašome patikrinkite, ar turite įdiegtą GD PHP plėtinį.',
+ 'server_upload_limit' => 'Serveris neleidžia įkelti tokio dydžio failų. Prašome bandykite mažesnį failo dydį.',
+ 'uploaded' => 'Serveris neleidžia įkelti tokio dydžio failų. Prašome bandykite mažesnį failo dydį.',
+ 'image_upload_error' => 'Įvyko klaida įkeliant vaizdą',
+ 'image_upload_type_error' => 'Vaizdo tipas, kurį norima įkelti, yra neteisingas',
+ 'file_upload_timeout' => 'Failo įkėlimo laikas baigėsi',
+
+ // Attachments
+ 'attachment_not_found' => 'Priedas nerastas',
+
+ // Pages
+ 'page_draft_autosave_fail' => 'Juodraščio išsaugoti nepavyko. Įsitikinkite, jog turite interneto ryšį prieš išsaugant šį paslapį.',
+ 'page_custom_home_deletion' => 'Negalima ištrinti šio puslapio, kol jis yra nustatytas kaip pagrindinis puslapis',
+
+ // Entities
+ 'entity_not_found' => 'Subjektas nerastas',
+ 'bookshelf_not_found' => 'Knygų lentyna nerasta',
+ 'book_not_found' => 'Knyga nerasta',
+ 'page_not_found' => 'Puslapis nerastas',
+ 'chapter_not_found' => 'Skyrius nerastas',
+ 'selected_book_not_found' => 'Pasirinkta knyga nerasta',
+ 'selected_book_chapter_not_found' => 'Pasirinkta knyga ar skyrius buvo nerasti',
+ 'guests_cannot_save_drafts' => 'Svečiai negali išsaugoti juodraščių',
+
+ // Users
+ 'users_cannot_delete_only_admin' => 'Negalite ištrinti vienintelio administratoriaus',
+ 'users_cannot_delete_guest' => 'Negalite ištrinti svečio naudotojo',
+
+ // Roles
+ 'role_cannot_be_edited' => 'Šio vaidmens negalima redaguoti',
+ 'role_system_cannot_be_deleted' => 'Šis vaidmuo yra sistemos vaidmuo ir jo negalima ištrinti',
+ 'role_registration_default_cannot_delete' => 'Šis vaidmuo negali būti ištrintas, kai yra nustatytas kaip numatytasis registracijos vaidmuo',
+ 'role_cannot_remove_only_admin' => 'Šis naudotojas yra vienintelis naudotojas, kuriam yra paskirtas administratoriaus vaidmuo. Paskirkite administratoriaus vaidmenį kitam naudotojui prieš bandant jį pašalinti.',
+
+ // Comments
+ 'comment_list' => 'Gaunant komentarus įvyko klaida.',
+ 'cannot_add_comment_to_draft' => 'Negalite pridėti komentaro juodraštyje',
+ 'comment_add' => 'Klaido įvyko pridedant/atnaujinant komantarą.',
+ 'comment_delete' => 'Trinant komentarą įvyko klaida.',
+ 'empty_comment' => 'Negalite pridėti tuščio komentaro.',
+
+ // Error pages
+ '404_page_not_found' => 'Puslapis nerastas',
+ 'sorry_page_not_found' => 'Atleiskite, puslapis, kurio ieškote, nerastas.',
+ 'sorry_page_not_found_permission_warning' => 'Jei tikėjotės, kad šis puslapis egzistuoja, galbūt neturite leidimo jo peržiūrėti.',
+ 'image_not_found' => 'Image Not Found',
+ 'image_not_found_subtitle' => 'Sorry, The image file you were looking for could not be found.',
+ 'image_not_found_details' => 'If you expected this image to exist it might have been deleted.',
+ 'return_home' => 'Grįžti į namus',
+ 'error_occurred' => 'Įvyko klaida',
+ 'app_down' => ':appName dabar yra apačioje',
+ 'back_soon' => 'Tai sugrįž greitai',
+
+ // API errors
+ 'api_no_authorization_found' => 'Užklausoje nerastas įgaliojimo prieigos raktas',
+ 'api_bad_authorization_format' => 'Užklausoje rastas prieigos raktas, tačiau formatas yra neteisingas',
+ 'api_user_token_not_found' => 'Pateiktam prieigos raktui nebuvo rastas atitinkamas API prieigos raktas',
+ 'api_incorrect_token_secret' => 'Pateiktas panaudoto API žetono slėpinys yra neteisingas',
+ 'api_user_no_api_permission' => 'API prieigos rakto savininkas neturi leidimo daryti API skambučius',
+ 'api_user_token_expired' => 'Prieigos rakto naudojimas baigė galioti',
+
+ // Settings & Maintenance
+ 'maintenance_test_email_failure' => 'Siunčiant bandymo email: įvyko klaida',
+
+];
--- /dev/null
+<?php
+/**
+ * Pagination Language Lines
+ * The following language lines are used by the paginator library to build
+ * the simple pagination links.
+ */
+return [
+
+ 'previous' => '« Ankstesnis',
+ 'next' => 'Kitas »',
+
+];
--- /dev/null
+<?php
+/**
+ * Password Reminder Language Lines
+ * The following language lines are the default lines which match reasons
+ * that are given by the password broker for a password update attempt has failed.
+ */
+return [
+
+ 'password' => 'Slaptažodis privalo būti mažiausiai aštuonių simbolių ir atitikti patvirtinimą.',
+ 'user' => "We can't find a user with that e-mail address.",
+ 'token' => 'Slaptažodžio nustatymo raktas yra neteisingas šiam elektroninio pašto adresui.',
+ 'sent' => 'Elektroniu paštu jums atsiuntėme slaptažodžio atkūrimo nuorodą!',
+ 'reset' => 'Jūsų slaptažodis buvo atkurtas!',
+
+];
--- /dev/null
+<?php
+/**
+ * Settings text strings
+ * Contains all text strings used in the general settings sections of BookStack
+ * including users and roles.
+ */
+return [
+
+ // Common Messages
+ 'settings' => 'Nustatymai',
+ 'settings_save' => 'Išsaugoti nustatymus',
+ 'settings_save_success' => 'Nustatymai išsaugoti',
+
+ // App Settings
+ 'app_customization' => 'Tinkinimas',
+ 'app_features_security' => 'Funkcijos ir sauga',
+ 'app_name' => 'Programos pavadinimas',
+ 'app_name_desc' => 'Šis pavadinimas yra rodomas antraštėje ir bet kuriuose sistemos siunčiamuose elektroniniuose laiškuose.',
+ 'app_name_header' => 'Rodyti pavadinimą antraštėje',
+ 'app_public_access' => 'Vieša prieiga',
+ 'app_public_access_desc' => 'Įjungus šią parinktį lankytojai, kurie nėra prisijungę, galės pasiekti BookStack egzemplioriaus turinį.',
+ 'app_public_access_desc_guest' => 'Prieiga viešiems lankytojams gali būti kontroliuojama per "Svečio" naudotoją.',
+ 'app_public_access_toggle' => 'Leisti viešą prieigą',
+ 'app_public_viewing' => 'Leisti viešą žiūrėjimą?',
+ 'app_secure_images' => 'Didesnio saugumo vaizdų įkėlimai',
+ 'app_secure_images_toggle' => 'Įgalinti didesnio saugumo vaizdų įkėlimus',
+ 'app_secure_images_desc' => 'Dėl veiklos priežasčių, visi vaizdai yra vieši. Šis pasirinkimas prideda atsitiktinę, sunkiai atspėjamą eilutę prieš vaizdo URL. Įsitikinkite, kad katalogų rodyklės neįgalintos, kad prieiga būtų lengvesnė.',
+ 'app_editor' => 'Puslapio redaktorius',
+ 'app_editor_desc' => 'Pasirinkite, kuris redaktorius bus naudojamas visų vartotojų redaguoti puslapiams.',
+ 'app_custom_html' => 'Pasirinktinis HTL antraštės turinys',
+ 'app_custom_html_desc' => 'Bet koks čia pridedamas turinys bus prisegamas apačioje <antraštės> kiekvieno puslapio skyriuje. Tai yra patogu svarbesniems stiliams arba pridedant analizės kodą.',
+ 'app_custom_html_disabled_notice' => 'Pasirinktinis HTML antraštės turinys yra išjungtas šiame nustatymų puslapyje užtikrinti, kad bet kokie negeri pokyčiai galėtų būti anuliuojami.',
+ 'app_logo' => 'Programos logotipas',
+ 'app_logo_desc' => 'Šis vaizdas turėtų būti 43px aukščio. <br> Dideli vaizdai bus sumažinti.',
+ 'app_primary_color' => 'Programos pagrindinė spalva',
+ 'app_primary_color_desc' => 'Nustato pagrindinę spalvą programai, įskaitant reklamjuostę, mygtukus ir nuorodas.',
+ 'app_homepage' => 'Programos pagrindinis puslapis',
+ 'app_homepage_desc' => 'Pasirinkite vaizdą rodyti pagrindiniame paslapyje vietoj numatyto vaizdo. Puslapio leidimai yra ignoruojami pasirinktiems puslapiams.',
+ 'app_homepage_select' => 'Pasirinkti puslapį',
+ 'app_footer_links' => 'Poraštės nuorodos',
+ 'app_footer_links_desc' => 'Pridėkite nuorodas, kurias norite pridėti svetainės poraštėje. Jos bus rodomos daugelio puslapių apačioje, įskaitant ir tuos, kurie nereikalauja prisijungimo. Jūs galite naudoti etiktę "trans::<key>", kad naudotis sistemos apibrėžtais vertimais. Pavyzdžiui: naudojimasis "trans::common.privacy_policy" bus pateiktas išverstu tekstu "Privatumo Politika" ir ""trans::common.terms_of_service" bus pateikta išverstu tekstu "Paslaugų Teikimo Sąlygos".',
+ 'app_footer_links_label' => 'Etiketės nuoroda',
+ 'app_footer_links_url' => 'Nuoroda URL',
+ 'app_footer_links_add' => 'Pridėti poraštes nuorodą',
+ 'app_disable_comments' => 'Išjungti komentarus',
+ 'app_disable_comments_toggle' => 'Išjungti komentarus',
+ 'app_disable_comments_desc' => 'Išjungti komentarus visuose programos puslapiuose. <br> Esantys komentarai nerodomi.',
+
+ // Color settings
+ 'content_colors' => 'Turinio spalvos',
+ 'content_colors_desc' => 'Nustato spalvas visiems elementams puslapio organizacijos herarchijoje. Rekomenduojama pasirinkti spalvas su panačiu šviesumu kaip numatytos spalvos, kad būtų lengviau skaityti.',
+ 'bookshelf_color' => 'Lentynos spalva',
+ 'book_color' => 'Knygos spalva',
+ 'chapter_color' => 'Skyriaus spalva',
+ 'page_color' => 'Puslapio spalva',
+ 'page_draft_color' => 'Puslapio juodraščio spalva',
+
+ // Registration Settings
+ 'reg_settings' => 'Registracija',
+ 'reg_enable' => 'Įgalinti registraciją',
+ 'reg_enable_toggle' => 'Įgalinti registraciją',
+ 'reg_enable_desc' => 'Kai registracija yra įgalinta, naudotojai gali prisiregistruoti kaip programos naudotojai. Registruojantis jiems suteikiamas vienintelis, nematytasis naudotojo vaidmuo.',
+ 'reg_default_role' => 'Numatytasis naudotojo vaidmuo po registracijos',
+ 'reg_enable_external_warning' => 'Ankstesnė parinktis nepaisoma, kai išorinis LDAP arba SAML autentifikavimas yra aktyvus. Vartotojo paskyra neegzistuojantiems nariams bus automatiškai sukurta, jei autentifikavimas naudojant naudojamą išorinę sistemą bus sėkmingas.',
+ 'reg_email_confirmation' => 'Elektroninio pašto patvirtinimas',
+ 'reg_email_confirmation_toggle' => 'Reikalauja elektroninio pašto patvirtinimo',
+ 'reg_confirm_email_desc' => 'Jei naudojamas domeno apribojimas, tada elektroninio pašto patvirtinimas bus reikalaujamas ir ši parinktis bus ignoruojama.',
+ 'reg_confirm_restrict_domain' => 'Domeno apribojimas',
+ 'reg_confirm_restrict_domain_desc' => 'Įveskite kableliais atskirtą elektroninio pašto domenų, kurių registravimą norite apriboti, sąrašą. Vartotojai išsiųs elektorinį laišką, kad patvirtintumėte jų adresą prieš leidžiant naudotis programa. <br> Prisiminkite, kad vartotojai galės pakeisti savo elektroninius paštus po sėkmingos registracijos.',
+ 'reg_confirm_restrict_domain_placeholder' => 'Nėra jokių apribojimų',
+
+ // Maintenance settings
+ 'maint' => 'Priežiūra',
+ 'maint_image_cleanup' => 'Išvalykite vaizdus',
+ 'maint_image_cleanup_desc' => "Scans page & revision content to check which images and drawings are currently in use and which images are redundant. Ensure you create a full database and image backup before running this.",
+ 'maint_delete_images_only_in_revisions' => 'Taip pat ištrinkite vaizdus, kurie yra tik senuose puslapių pataisymuose',
+ 'maint_image_cleanup_run' => 'Paleisti valymą',
+ 'maint_image_cleanup_warning' => ':count potencialiai nepanaudoti vaizdai rasti. Ar esate tikri, kad norite ištrinti šiuos vaizdus?',
+ 'maint_image_cleanup_success' => ':count potencialiai nepanaudoti vaizdai rasti ir ištrinti!',
+ 'maint_image_cleanup_nothing_found' => 'Nerasta nepanaudotų vaizdų, niekas neištrinta!',
+ 'maint_send_test_email' => 'Siųsti bandomąjį elektroninį laišką',
+ 'maint_send_test_email_desc' => 'ai siunčia bandomąjį elektroninį laišką elektroninio pašto adresu, nurodytu jūsų profilyje.',
+ 'maint_send_test_email_run' => 'Siųsti bandomąjį elektroninį laišką',
+ 'maint_send_test_email_success' => 'Elektroninis laiškas išsiųstas :address',
+ 'maint_send_test_email_mail_subject' => 'Bandomasis elektroninis laiškas',
+ 'maint_send_test_email_mail_greeting' => 'Elektroninio laiško pristatymas veikia!',
+ 'maint_send_test_email_mail_text' => 'Sveikiname! Kadangi gavote šį elektroninio pašto pranešimą, jūsų elektroninio pašto nustatymai buvo sukonfigūruoti teisingai.',
+ 'maint_recycle_bin_desc' => 'Ištrintos lentynos, knygos, skyriai ir puslapiai yra perkeliami į šiukšliadėžę tam, kad jie galėtų būti atkurti arba ištrinti visam laikui. Senesni elementai, esantys šiukšliadėžėje, gali būti automatiškai panaikinti po tam tikro laiko priklausomai nuo sistemos konfigūracijos.',
+ 'maint_recycle_bin_open' => 'Atidaryti šiukšliadėžę',
+
+ // Recycle Bin
+ 'recycle_bin' => 'Šiukšliadėžė',
+ 'recycle_bin_desc' => 'Čia gali atkurti elementus, kurie buvo ištrinti arba pasirinkti pašalinti juos iš sistemos visam laikui. Šis sąrašas yra nefiltruotas kaip kitie panašus veiklos sąrašai sistemoje, kuriems yra taikomi leidimo filtrai.',
+ 'recycle_bin_deleted_item' => 'Ištrintas elementas',
+ 'recycle_bin_deleted_parent' => 'Parent',
+ 'recycle_bin_deleted_by' => 'Ištrynė',
+ 'recycle_bin_deleted_at' => 'Panaikinimo laikas',
+ 'recycle_bin_permanently_delete' => 'Ištrinti visam laikui',
+ 'recycle_bin_restore' => 'Atkurti',
+ 'recycle_bin_contents_empty' => 'Šiukšliadėžė šiuo metu yra tuščia',
+ 'recycle_bin_empty' => 'Ištuštinti šiukšliadėžę',
+ 'recycle_bin_empty_confirm' => 'Tai visam laikui sunaikins visus elementus, esančius šiukšliadėžėje, įskaitant kiekvieno elemento turinį. Ar esate tikri, jog norite ištuštinti šiukšliadėžę?',
+ 'recycle_bin_destroy_confirm' => 'Šis veiksmas visam laikui ištrins šį elementą iš sistemos kartu su bet kuriais elementais įvardintais žemiau ir jūs nebegalėsite atkurti jo bei jo turinio. Ar esate tikri, jog norite visam laikui ištrinti šį elementą?',
+ 'recycle_bin_destroy_list' => 'Elementai panaikinimui',
+ 'recycle_bin_restore_list' => 'Elementai atkūrimui',
+ 'recycle_bin_restore_confirm' => 'Šis veiksmas atkurs ištrintą elementą ir perkels jį atgal į jo originalią vietą. Jei originali vieta buvo ištrinta ir šiuo metu yra šiukšliadėžėje, ji taip pat turės būti atkurta.',
+ 'recycle_bin_restore_deleted_parent' => 'Pagrindinis elementas buvo ištrintas. Šie elementai liks ištrinti iki tol, kol bus atkurtas pagrindinis elementas.',
+ 'recycle_bin_restore_parent' => 'Restore Parent',
+ 'recycle_bin_destroy_notification' => 'Ištrinti :count visus elementus, esančius šiukšliadėžėje.',
+ 'recycle_bin_restore_notification' => 'Atkurti :count visus elementus, esančius šiukšliadėžėje.',
+
+ // Audit Log
+ 'audit' => 'Audito seka',
+ 'audit_desc' => 'Ši audito seka rodo sąrašą veiklų, rastų sistemoje. Šis sąrašas yra nefiltruotas kaip kitie panašus veiklos sąrašai sistemoje, kuriems yra taikomi leidimo filtrai.',
+ 'audit_event_filter' => 'Įvykio filtras',
+ 'audit_event_filter_no_filter' => 'Be filtrų',
+ 'audit_deleted_item' => 'Ištrintas elementas',
+ 'audit_deleted_item_name' => 'Vardas: :name',
+ 'audit_table_user' => 'Naudotojas',
+ 'audit_table_event' => 'Įvykis',
+ 'audit_table_related' => 'Susijęs elementas arba detalė',
+ 'audit_table_ip' => 'IP Address',
+ 'audit_table_date' => 'Veiklos data',
+ 'audit_date_from' => 'Datos seka nuo',
+ 'audit_date_to' => 'Datos seka iki',
+
+ // Role Settings
+ 'roles' => 'Vaidmenys',
+ 'role_user_roles' => 'Naudotojo vaidmenys',
+ 'role_create' => 'Sukurti naują vaidmenį',
+ 'role_create_success' => 'Vaidmuo sukurtas sėkmingai',
+ 'role_delete' => 'Ištrinti vaidmenį',
+ 'role_delete_confirm' => 'Tai ištrins vaidmenį vardu\':roleName\'.',
+ 'role_delete_users_assigned' => 'Šis vaidmuo turi :userCount naudotojus priskirtus prie jo. Jeigu norite naudotojus perkelti iš šio vaidmens, pasirinkite naują vaidmenį apačioje.',
+ 'role_delete_no_migration' => "Don't migrate users",
+ 'role_delete_sure' => 'Ar esate tikri, jog norite ištrinti šį vaidmenį?',
+ 'role_delete_success' => 'Vaidmuo ištrintas sėkmingai',
+ 'role_edit' => 'Redaguoti vaidmenį',
+ 'role_details' => 'Vaidmens detalės',
+ 'role_name' => 'Vaidmens pavadinimas',
+ 'role_desc' => 'Trumpas vaidmens aprašymas',
+ 'role_mfa_enforced' => 'Requires Multi-Factor Authentication',
+ 'role_external_auth_id' => 'Išorinio autentifikavimo ID',
+ 'role_system' => 'Sistemos leidimai',
+ 'role_manage_users' => 'Tvarkyti naudotojus',
+ 'role_manage_roles' => 'Tvarkyti vaidmenis ir vaidmenų leidimus',
+ 'role_manage_entity_permissions' => 'Tvarkyti visus knygų, skyrių ir puslapių leidimus',
+ 'role_manage_own_entity_permissions' => 'Tvarkyti savo knygos, skyriaus ir puslapių leidimus',
+ 'role_manage_page_templates' => 'Tvarkyti puslapių šablonus',
+ 'role_access_api' => 'Gauti prieigą prie sistemos API',
+ 'role_manage_settings' => 'Tvarkyti programos nustatymus',
+ 'role_export_content' => 'Export content',
+ 'role_asset' => 'Nuosavybės leidimai',
+ 'roles_system_warning' => 'Būkite sąmoningi, kad prieiga prie bet kurio iš trijų leidimų viršuje gali leisti naudotojui pakeisti jų pačių privilegijas arba kitų privilegijas sistemoje. Paskirkite vaidmenis su šiais leidimais tik patikimiems naudotojams.',
+ 'role_asset_desc' => 'Šie leidimai kontroliuoja numatytą prieigą į nuosavybę, esančią sistemoje. Knygų, skyrių ir puslapių leidimai nepaisys šių leidimų.',
+ 'role_asset_admins' => 'Administratoriams automatiškai yra suteikiama prieiga prie viso turinio, tačiau šie pasirinkimai gali rodyti arba slėpti vartotojo sąsajos parinktis.',
+ 'role_all' => 'Visi',
+ 'role_own' => 'Nuosavi',
+ 'role_controlled_by_asset' => 'Kontroliuojami nuosavybės, į kurią yra įkelti',
+ 'role_save' => 'Išsaugoti vaidmenį',
+ 'role_update_success' => 'Vaidmuo atnaujintas sėkmingai',
+ 'role_users' => 'Naudotojai šiame vaidmenyje',
+ 'role_users_none' => 'Šiuo metu prie šio vaidmens nėra priskirta naudotojų',
+
+ // Users
+ 'users' => 'Naudotojai',
+ 'user_profile' => 'Naudotojo profilis',
+ 'users_add_new' => 'Pridėti naują naudotoją',
+ 'users_search' => 'Ieškoti naudotojų',
+ 'users_latest_activity' => 'Naujausia veikla',
+ 'users_details' => 'Naudotojo detalės',
+ 'users_details_desc' => 'Nustatykite rodomąjį vardą ir elektroninio pašto adresą šiam naudotojui. Šis elektroninio pašto adresas bus naudojamas prisijungimui prie aplikacijos.',
+ 'users_details_desc_no_email' => 'Nustatykite rodomąjį vardą šiam naudotojui, kad kiti galėtų jį atpažinti.',
+ 'users_role' => 'Naudotojo vaidmenys',
+ 'users_role_desc' => 'Pasirinkite, prie kokių vaidmenų bus priskirtas šis naudotojas. Jeigu naudotojas yra priskirtas prie kelių vaidmenų, leidimai iš tų vaidmenų susidės ir jie gaus visus priskirtų vaidmenų gebėjimus.',
+ 'users_password' => 'Naudotojo slaptažodis',
+ 'users_password_desc' => 'Susikurkite slaptažodį, kuris bus naudojamas prisijungti prie aplikacijos. Slaptažodis turi būti bent 6 simbolių ilgio.',
+ 'users_send_invite_text' => 'Jūs galite pasirinkti nusiųsti šiam naudotojui kvietimą elektroniniu paštu, kuris leistų jiems patiems susikurti slaptažodį. Priešingu atveju slaptažodį galite sukurti patys.',
+ 'users_send_invite_option' => 'Nusiųsti naudotojui kvietimą elektroniniu paštu',
+ 'users_external_auth_id' => 'Išorinio autentifikavimo ID',
+ 'users_external_auth_id_desc' => 'Tai yra ID, naudojamas norint suderinti šį naudotoją bendraujant su jūsų išorinio autentifikavimo sistema.',
+ 'users_password_warning' => 'Užpildykite laukelį apačioje tik tuo atveju, jeigu norite pakeisti savo slaptažodį.',
+ 'users_system_public' => 'Šis naudotojas atstovauja svečius, kurie aplanko jūsų egzempliorių. Jis negali būti naudojamas prisijungimui, tačiau yra priskiriamas automatiškai.',
+ 'users_delete' => 'Ištrinti naudotoją',
+ 'users_delete_named' => 'Ištrinti naudotoją :userName',
+ 'users_delete_warning' => 'Tai pilnai ištrins šį naudotoją vardu \':userName\' iš sistemos.',
+ 'users_delete_confirm' => 'Ar esate tikri, jog norite ištrinti šį naudotoją?',
+ 'users_migrate_ownership' => 'Perkelti nuosavybę',
+ 'users_migrate_ownership_desc' => 'Pasirinkite naudotoją, jeigu norite, kad kitas naudotojas taptų visų elementų, šiuo metu priklausančių šiam naudotojui, savininku.',
+ 'users_none_selected' => 'Naudotojas nepasirinktas',
+ 'users_delete_success' => 'Naudotojas sėkmingai pašalintas',
+ 'users_edit' => 'Redaguoti naudotoją',
+ 'users_edit_profile' => 'Redaguoti profilį',
+ 'users_edit_success' => 'Naudotojas sėkmingai atnaujintas',
+ 'users_avatar' => 'Naudotojo pseudoportretas',
+ 'users_avatar_desc' => 'Pasirinkite nuotrauką, pavaizduojančią šį naudotoją. Nuotrauka turi būti maždaug 256px kvadratas.',
+ 'users_preferred_language' => 'Norima kalba',
+ 'users_preferred_language_desc' => 'Ši parinktis pakeis kalbą, naudojamą naudotojo sąsajoje aplikacijoje. Tai neturės įtakos jokiam vartotojo sukurtam turiniui.',
+ 'users_social_accounts' => 'Socialinės paskyros',
+ 'users_social_accounts_info' => 'Čia galite susieti savo kitas paskyras greitesniam ir lengvesniam prisijungimui. Atjungus paskyrą čia neatšaukiama anksčiau leista prieiga. Atšaukite prieigą iš profilio nustatymų prijungtoje socialinėje paskyroje.',
+ 'users_social_connect' => 'Susieti paskyrą',
+ 'users_social_disconnect' => 'Atskirti paskyrą',
+ 'users_social_connected' => ':socialAccount paskyra buvo sėkmingai susieta su jūsų profiliu.',
+ 'users_social_disconnected' => ':socialAccount paskyra buvo sėkmingai atskirta nuo jūsu profilio.',
+ 'users_api_tokens' => 'API sąsajos prieigos raktai',
+ 'users_api_tokens_none' => 'Jokie API sąsajos prieigos raktai nebuvo sukurti šiam naudotojui',
+ 'users_api_tokens_create' => 'Sukurti prieigos raktą',
+ 'users_api_tokens_expires' => 'Baigia galioti',
+ 'users_api_tokens_docs' => 'API dokumentacija',
+ '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',
+
+ // API Tokens
+ 'user_api_token_create' => 'Sukurti API sąsajos prieigos raktą',
+ 'user_api_token_name' => 'Pavadinimas',
+ 'user_api_token_name_desc' => 'Suteikite savo prieigos raktui perskaitomą pavadinimą kaip priminimą ateičiai apie jo numatytą tikslą.',
+ 'user_api_token_expiry' => 'Galiojimo laikas',
+ 'user_api_token_expiry_desc' => 'Nustatykite datą kada šis prieigos raktas baigs galioti. Po šios datos, prašymai, atlikti naudojant šį prieigos raktą daugiau nebeveiks. Jeigu šį laukelį paliksite tuščią, galiojimo laikas bus nustatytas 100 metų į ateitį.',
+ 'user_api_token_create_secret_message' => 'Iš karto sukūrus šį prieigos raktą, bus sukurtas ir rodomas "Priegos rakto ID" ir "Prieigos rakto slėpinys". Prieigos rakto slėpinys bus rodomas tik vieną kartą, todėl būtinai nukopijuokite jį kur nors saugioje vietoje.',
+ 'user_api_token_create_success' => 'API sąsajos prieigos raktas sėkmingai sukurtas',
+ 'user_api_token_update_success' => 'API sąsajos prieigos raktas sėkmingai atnaujintas',
+ 'user_api_token' => 'API sąsajos prieigos raktas',
+ 'user_api_token_id' => 'Prieigos rakto ID',
+ 'user_api_token_id_desc' => 'Tai neredaguojamas sistemos sugeneruotas identifikatorius šiam prieigos raktui, kurį reikės pateikti API užklausose.',
+ 'user_api_token_secret' => 'Priegos rakto slėpinys',
+ 'user_api_token_secret_desc' => 'Tai yra sistemos sukurtas šio priegos rakto slėpinys, kurią reikės pateikti API užklausose. Tai bus rodoma tik šį kartą, todėl nukopijuokite šią vertę į saugią vietą.',
+ 'user_api_token_created' => 'Prieigos raktas sukurtas :timeAgo',
+ 'user_api_token_updated' => 'Prieigos raktas atnaujintas :timeAgo',
+ 'user_api_token_delete' => 'Ištrinti prieigos raktą',
+ 'user_api_token_delete_warning' => 'Tai pilnai ištrins šį API sąsajos prieigos raktą pavadinimu \':tokenName\' iš sistemos.',
+ 'user_api_token_delete_confirm' => 'Ar esate tikri, jog norite ištrinti šį API sąsajos prieigos raktą?',
+ 'user_api_token_delete_success' => 'API sąsajos prieigos raktas sėkmingai ištrintas',
+
+ //! 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',
+ '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',
+ 'ru' => 'Русский',
+ 'sk' => 'Slovensky',
+ 'sl' => 'Slovenščina',
+ 'sv' => 'Svenska',
+ 'tr' => 'Türkçe',
+ 'uk' => 'Українська',
+ 'vi' => 'Tiếng Việt',
+ 'zh_CN' => '简体中文',
+ 'zh_TW' => '繁體中文',
+ ]
+ //!////////////////////////////////
+];
--- /dev/null
+<?php
+/**
+ * Validation Lines
+ * The following language lines contain the default error messages used by
+ * the validator class. Some of these rules have multiple versions such
+ * as the size rules. Feel free to tweak each of these messages here.
+ */
+return [
+
+ // Standard laravel validation lines
+ 'accepted' => ':attribute turi būti priimtas.',
+ 'active_url' => ':attribute nėra tinkamas URL.',
+ 'after' => ':attribute turi būti data po :date.',
+ 'alpha' => ':attribute turi būti sudarytis tik iš raidžių.',
+ 'alpha_dash' => ':attribute turi būti sudarytas tik iš raidžių, skaičių, brūkšnelių ir pabraukimų.',
+ 'alpha_num' => ':attribute turi būti sudarytas tik iš raidžių ir skaičių.',
+ 'array' => ':attribute turi būti masyvas.',
+ 'backup_codes' => 'The provided code is not valid or has already been used.',
+ 'before' => ':attribute turi būti data anksčiau negu :date.',
+ 'between' => [
+ 'numeric' => ':attribute turi būti tarp :min ir :max.',
+ 'file' => ':attribute turi būti tarp :min ir :max kilobaitų.',
+ 'string' => ':attribute turi būti tarp :min ir :max simbolių.',
+ 'array' => ':attribute turi turėti tarp :min ir :max elementų.',
+ ],
+ 'boolean' => ':attribute laukas turi būti tiesa arba melas.',
+ 'confirmed' => ':attribute patvirtinimas nesutampa.',
+ 'date' => ':attribute nėra tinkama data.',
+ 'date_format' => ':attribute neatitinka formato :format.',
+ 'different' => ':attribute ir :other turi būti skirtingi.',
+ 'digits' => ':attribute turi būti :digits skaitmenų.',
+ 'digits_between' => ':attribute turi būti tarp :min ir :max skaitmenų.',
+ 'email' => ':attribute turi būti tinkamas elektroninio pašto adresas.',
+ 'ends_with' => ':attribute turi pasibaigti vienu iš šių: :values',
+ 'filled' => ':attribute laukas yra privalomas.',
+ 'gt' => [
+ 'numeric' => ':attribute turi būti didesnis negu :value.',
+ 'file' => ':attribute turi būti didesnis negu :value kilobaitai.',
+ 'string' => ':attribute turi būti didesnis negu :value simboliai.',
+ 'array' => ':attribute turi turėti daugiau negu :value elementus.',
+ ],
+ 'gte' => [
+ 'numeric' => ':attribute turi būti didesnis negu arba lygus :value.',
+ 'file' => ':attribute turi būti didesnis negu arba lygus :value kilobaitams.',
+ 'string' => ':attribute turi būti didesnis negu arba lygus :value simboliams.',
+ 'array' => ':attribute turi turėti :value elementus arba daugiau.',
+ ],
+ 'exists' => 'Pasirinktas :attribute yra klaidingas.',
+ 'image' => ':attribute turi būti paveikslėlis.',
+ 'image_extension' => ':attribute turi būti tinkamas ir palaikomas vaizdo plėtinys.',
+ 'in' => 'Pasirinktas :attribute yra klaidingas.',
+ 'integer' => ':attribute turi būti sveikasis skaičius.',
+ 'ip' => ':attribute turi būti tinkamas IP adresas.',
+ 'ipv4' => ':attribute turi būti tinkamas IPv4 adresas.',
+ 'ipv6' => ':attribute turi būti tinkamas IPv6 adresas.',
+ 'json' => ':attribute turi būti tinkama JSON eilutė.',
+ 'lt' => [
+ 'numeric' => ':attribute turi būti mažiau negu :value.',
+ 'file' => ':attribute turi būti mažiau negu :value kilobaitai.',
+ 'string' => ':attribute turi būti mažiau negu :value simboliai.',
+ 'array' => ':attribute turi turėti mažiau negu :value elementus.',
+ ],
+ 'lte' => [
+ 'numeric' => ':attribute turi būti mažiau arba lygus :value.',
+ 'file' => ':attribute turi būti mažiau arba lygus :value kilobaitams.',
+ 'string' => ':attribute turi būti mažiau arba lygus :value simboliams.',
+ 'array' => ':attribute negali turėti daugiau negu :value elementų.',
+ ],
+ 'max' => [
+ 'numeric' => ':attribute negali būti didesnis negu :max.',
+ 'file' => ':attribute negali būti didesnis negu :max kilobaitai.',
+ 'string' => ':attribute negali būti didesnis negu :max simboliai.',
+ 'array' => ':attribute negali turėti daugiau negu :max elementų.',
+ ],
+ 'mimes' => ':attribute turi būti tipo failas: :values.',
+ 'min' => [
+ 'numeric' => ':attribute turi būti mažiausiai :min.',
+ 'file' => ':attribute turi būti mažiausiai :min kilobaitų.',
+ 'string' => ':attribute turi būti mažiausiai :min simbolių.',
+ 'array' => ':attribute turi turėti mažiausiai :min elementus.',
+ ],
+ 'not_in' => 'Pasirinktas :attribute yra klaidingas.',
+ 'not_regex' => ':attribute formatas yra klaidingas.',
+ 'numeric' => ':attribute turi būti skaičius.',
+ 'regex' => ':attribute formatas yra klaidingas.',
+ 'required' => ':attribute laukas yra privalomas.',
+ 'required_if' => ':attribute laukas yra privalomas kai :other yra :value.',
+ 'required_with' => ':attribute laukas yra privalomas kai :values yra.',
+ 'required_with_all' => ':attribute laukas yra privalomas kai :values yra.',
+ 'required_without' => ':attribute laukas yra privalomas kai nėra :values.',
+ 'required_without_all' => ':attribute laukas yra privalomas kai nėra nei vienos :values.',
+ 'same' => ':attribute ir :other turi sutapti.',
+ 'safe_url' => 'Pateikta nuoroda gali būti nesaugi.',
+ 'size' => [
+ 'numeric' => ':attribute turi būti :size.',
+ 'file' => ':attribute turi būti :size kilobaitų.',
+ 'string' => ':attribute turi būti :size simbolių.',
+ 'array' => ':attribute turi turėti :size elementus.',
+ ],
+ 'string' => ':attribute turi būti eilutė.',
+ 'timezone' => ':attribute turi būti tinkama zona.',
+ 'totp' => 'The provided code is not valid or has expired.',
+ 'unique' => ':attribute jau yra paimtas.',
+ 'url' => ':attribute formatas yra klaidingas.',
+ 'uploaded' => 'Šis failas negali būti įkeltas. Serveris gali nepriimti tokio dydžio failų.',
+
+ // Custom validation lines
+ 'custom' => [
+ 'password-confirm' => [
+ 'required_with' => 'Reikalingas slaptažodžio patvirtinimas',
+ ],
+ ],
+
+ // Custom validation attributes
+ 'attributes' => [],
+];
'favourite_add_notification' => '":name" ir pievienots jūsu favorītiem',
'favourite_remove_notification' => '":name" ir izņemts no jūsu favorītiem',
+ // MFA
+ 'mfa_setup_method_notification' => '2FA funkcija aktivizēta',
+ 'mfa_remove_method_notification' => '2FA funkcija noņemta',
+
// Other
'commented_on' => 'komentēts',
'permissions_update' => 'atjaunoja atļaujas',
'user_invite_page_welcome' => 'Sveicināti :appName!',
'user_invite_page_text' => 'Lai pabeigtu profila izveidi un piekļūtu :appName ir jāizveido parole.',
'user_invite_page_confirm_button' => 'Apstiprināt paroli',
- 'user_invite_success' => 'Parole iestatīta, tagad varat piekļūt :appName!'
+ 'user_invite_success' => 'Parole iestatīta, tagad varat piekļūt :appName!',
+
+ // Multi-factor Authentication
+ 'mfa_setup' => 'Iestati divfaktoru autentifikāciju (2FA)',
+ 'mfa_setup_desc' => 'Iestati divfaktoru autentifikāciju kā papildus drošību tavam lietotāja kontam.',
+ 'mfa_setup_configured' => 'Divfaktoru autentifikācija jau ir nokonfigurēta',
+ 'mfa_setup_reconfigure' => 'Mainīt 2FA konfigurāciju',
+ 'mfa_setup_remove_confirmation' => 'Vai esi drošs, ka vēlies noņemt divfaktoru autentifikāciju?',
+ '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.',
];
\ No newline at end of file
'reset' => 'Atiestatīt',
'remove' => 'Noņemt',
'add' => 'Pievienot',
+ 'configure' => 'Mainīt konfigurāciju',
'fullscreen' => 'Pilnekrāns',
'favourite' => 'Pievienot favorītiem',
'unfavourite' => 'Noņemt no favorītiem',
'no_activity' => 'Nav skatāmu darbību',
'no_items' => 'Vienumi nav pieejami',
'back_to_top' => 'Uz augšu',
+ 'skip_to_main_content' => 'Pāriet uz saturu',
'toggle_details' => 'Rādīt aprakstu',
'toggle_thumbnails' => 'Iezīmēt sīkatēlus',
'details' => 'Sīkāka informācija',
'export_html' => 'Pilna satura web fails',
'export_pdf' => 'PDF fails',
'export_text' => 'Vienkāršs teksta fails',
+ 'export_md' => 'Markdown fails',
// Permissions and restrictions
'permissions' => 'Atļaujas',
'shelves_permissions' => 'Grāmatplaukta atļaujas',
'shelves_permissions_updated' => 'Grāmatplaukta atļaujas atjauninātas',
'shelves_permissions_active' => 'Grāmatplaukta atļaujas ir aktīvas',
+ 'shelves_permissions_cascade_warning' => 'Grāmatu plauktu atļaujas netiek automātiski pārvietotas uz grāmatām. Tas ir tāpēc, ka grāmata var atrasties vairākos plauktos. Tomēr atļaujas var nokopēt uz plauktam pievienotajām grāmatām, izmantojot zemāk norādīto opciju.',
'shelves_copy_permissions_to_books' => 'Kopēt grāmatplaukta atļaujas uz grāmatām',
'shelves_copy_permissions' => 'Kopēt atļaujas',
'shelves_copy_permissions_explain' => 'Šis piemēros pašreizējās grāmatplaukta piekļuves tiesības visām tajā esošajām grāmatām. Pirms ieslēgšanas pārliecinieties, ka ir saglabātas izmaiņas grāmatplaukta piekļuves tiesībām.',
'recycle_bin' => 'Miskaste',
'recycle_bin_desc' => 'Te jūs varat atjaunot dzēstās vienības vai arī izdzēst tās no sistēmas pilnībā. Šis saraksts nav filtrēts atšķirībā no līdzīgiem darbību sarakstiem sistēmā, kur ir piemēroti piekļuves tiesību filtri.',
'recycle_bin_deleted_item' => 'Dzēsta vienība',
+ 'recycle_bin_deleted_parent' => 'Parent',
'recycle_bin_deleted_by' => 'Izdzēsa',
'recycle_bin_deleted_at' => 'Dzēšanas laiks',
'recycle_bin_permanently_delete' => 'Neatgriezeniski izdzēst',
'recycle_bin_restore_list' => 'Atjaunojamās vienības',
'recycle_bin_restore_confirm' => 'Šī darbība atjaunos dzēsto vienību, tai skaitā visus tai pakārtotos elementus, uz tās sākotnējo atrašanās vietu. Ja sākotnējā atrašanās vieta ir izdzēsta un atrodas miskastē, būs nepieciešams atjaunot arī to.',
'recycle_bin_restore_deleted_parent' => 'Šo elementu saturošā vienība arī ir dzēsta. Tas paliks dzēsts līdz šī saturošā vienība arī ir atjaunota.',
+ 'recycle_bin_restore_parent' => 'Restore Parent',
'recycle_bin_destroy_notification' => 'Dzēstas kopā :count vienības no miskastes.',
'recycle_bin_restore_notification' => 'Atjaunotas kopā :count vienības no miskastes.',
'audit_table_user' => 'Lietotājs',
'audit_table_event' => 'Notikums',
'audit_table_related' => 'Saistīta vienība vai detaļa',
+ 'audit_table_ip' => 'IP Address',
'audit_table_date' => 'Notikuma datums',
'audit_date_from' => 'Datums no',
'audit_date_to' => 'Datums līdz',
'role_details' => 'Informācija par grupu',
'role_name' => 'Grupas nosaukums',
'role_desc' => 'Īss grupas apaksts',
+ 'role_mfa_enforced' => 'Requires Multi-Factor Authentication',
'role_external_auth_id' => 'Ārējais autentifikācijas ID',
'role_system' => 'Sistēmas atļaujas',
'role_manage_users' => 'Pārvaldīt lietotājus',
'role_manage_page_templates' => 'Pārvaldīt lapas veidnes',
'role_access_api' => 'Piekļūt sistēmas API',
'role_manage_settings' => 'Pārvaldīt iestatījumus',
+ 'role_export_content' => 'Export content',
'role_asset' => 'Resursa piekļuves tiesības',
'roles_system_warning' => 'Jebkuras no trīs augstāk redzamajām atļaujām dod iespēju lietotājam mainīt savas un citu lietotāju sistēmas atļaujas. Pievieno šīs grupu atļaujas tikai tiem lietotājiem, kuriem uzticies.',
'role_asset_desc' => 'Šīs piekļuves tiesības kontrolē noklusēto piekļuvi sistēmas resursiem. Grāmatām, nodaļām un lapām norādītās tiesības būs pārākas par šīm.',
'users_api_tokens_create' => 'Izveidot žetonu',
'users_api_tokens_expires' => 'Derīguma termiņš',
'users_api_tokens_docs' => 'API dokumentācija',
+ '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',
// API Tokens
'user_api_token_create' => 'Izveidot API žetonu',
'it' => 'Italian',
'ja' => '日本語',
'ko' => '한국어',
+ 'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nl' => 'Nederlands',
'nb' => 'Norsk (Bokmål)',
'alpha_dash' => ':attribute var saturēt tikai burtus, ciparus, domuzīmes un apakš svītras.',
'alpha_num' => ':attribute var saturēt tikai burtus un ciparus.',
'array' => ':attribute ir jābūt masīvam.',
+ 'backup_codes' => 'Ievadītais kods nav derīgs vai arī jau ir izmantots.',
'before' => ':attribute jābūt datumam pirms :date.',
'between' => [
'numeric' => ':attribute jābūt starp :min un :max.',
],
'string' => ':attribute jābūt teksta virknei.',
'timezone' => ':attribute jābūt derīgai zonai.',
+ 'totp' => 'Ievadītais kods nav derīgs.',
'unique' => ':attribute jau ir aizņemts.',
'url' => ':attribute formāts nav derīgs.',
'uploaded' => 'Fails netika ielādēts. Serveris nevar pieņemt šāda izmēra failus.',
'bookshelf_delete_notification' => 'Bokhyllen ble slettet',
// Favourites
- 'favourite_add_notification' => '":name" has been added to your favourites',
- 'favourite_remove_notification' => '":name" has been removed from your favourites',
+ 'favourite_add_notification' => '«:name» ble lagt til i dine favoritter',
+ 'favourite_remove_notification' => '«:name» ble fjernet fra dine favoritter',
+
+ // MFA
+ 'mfa_setup_method_notification' => 'Flerfaktor-metoden ble konfigurert',
+ 'mfa_remove_method_notification' => 'Flerfaktor-metoden ble fjernet',
// Other
'commented_on' => 'kommenterte på',
'user_invite_page_welcome' => 'Velkommen til :appName!',
'user_invite_page_text' => 'For å fullføre prosessen må du oppgi et passord som sikrer din konto på :appName for fremtidige besøk.',
'user_invite_page_confirm_button' => 'Bekreft passord',
- 'user_invite_success' => 'Passordet er angitt, du kan nå bruke :appName!'
+ 'user_invite_success' => 'Passordet er angitt, du kan nå bruke :appName!',
+
+ // Multi-factor Authentication
+ 'mfa_setup' => 'Konfigurer flerfaktor-autentisering',
+ 'mfa_setup_desc' => 'Konfigurer flerfaktor-autentisering som et ekstra lag med sikkerhet for brukerkontoen din.',
+ 'mfa_setup_configured' => 'Allerede konfigurert',
+ 'mfa_setup_reconfigure' => 'Omkonfigurer',
+ 'mfa_setup_remove_confirmation' => 'Er du sikker på at du vil deaktivere denne flerfaktor-autentiseringsmetoden?',
+ 'mfa_setup_action' => 'Konfigurasjon',
+ 'mfa_backup_codes_usage_limit_warning' => 'Du har mindre enn 5 sikkerhetskoder igjen; vennligst generer og lagre ett nytt sett før du går tom for koder, for å unngå å bli låst ute av kontoen din.',
+ 'mfa_option_totp_title' => 'Mobilapplikasjon',
+ 'mfa_option_totp_desc' => 'For å bruke flerfaktorautentisering trenger du en mobilapplikasjon som støtter TOTP-teknologien, slik som Google Authenticator, Authy eller Microsoft Authenticator.',
+ 'mfa_option_backup_codes_title' => 'Sikkerhetskoder',
+ 'mfa_option_backup_codes_desc' => 'Lagre sikkerhetskoder til engangsbruk på et trygt sted, disse kan du bruke for å verifisere identiteten din.',
+ 'mfa_gen_confirm_and_enable' => 'Bekreft og aktiver',
+ 'mfa_gen_backup_codes_title' => 'Konfigurasjon av sikkerhetskoder',
+ 'mfa_gen_backup_codes_desc' => 'Lagre nedeforstående liste med koder på et trygt sted. Når du skal ha tilgang til systemet kan du bruke en av disse som en faktor under innlogging.',
+ 'mfa_gen_backup_codes_download' => 'Last ned koder',
+ 'mfa_gen_backup_codes_usage_warning' => 'Hver kode kan kun brukes en gang',
+ 'mfa_gen_totp_title' => 'Oppsett for mobilapplikasjon',
+ 'mfa_gen_totp_desc' => 'For å bruke flerfaktorautentisering trenger du en mobilapplikasjon som støtter TOTP-teknologien, slik som Google Authenticator, Authy eller Microsoft Authenticator.',
+ 'mfa_gen_totp_scan' => 'Scan QR-koden nedenfor med valgt TOTP-applikasjon for å starte.',
+ 'mfa_gen_totp_verify_setup' => 'Bekreft oppsett',
+ 'mfa_gen_totp_verify_setup_desc' => 'Bekreft at oppsettet fungerer ved å skrive inn koden fra TOTP-applikasjonen i boksen nedenfor:',
+ 'mfa_gen_totp_provide_code_here' => 'Skriv inn den genererte koden her',
+ 'mfa_verify_access' => 'Bekreft tilgang',
+ 'mfa_verify_access_desc' => 'Brukerkontoen din krever at du bekrefter din identitet med en ekstra autentiseringsfaktor før du får tilgang. Bekreft identiteten med en av dine konfigurerte metoder for å fortsette.',
+ 'mfa_verify_no_methods' => 'Ingen metoder er konfigurert',
+ 'mfa_verify_no_methods_desc' => 'Ingen flerfaktorautentiseringsmetoder er satt opp for din konto. Du må sette opp minst en metode for å få tilgang.',
+ 'mfa_verify_use_totp' => 'Bekreft med mobilapplikasjon',
+ 'mfa_verify_use_backup_codes' => 'Bekreft med sikkerhetskode',
+ 'mfa_verify_backup_code' => 'Sikkerhetskode',
+ 'mfa_verify_backup_code_desc' => 'Skriv inn en av dine ubrukte sikkerhetskoder under:',
+ 'mfa_verify_backup_code_enter_here' => 'Skriv inn sikkerhetskode her',
+ 'mfa_verify_totp_desc' => 'Skriv inn koden, generert ved hjelp av mobilapplikasjonen, nedenfor:',
+ 'mfa_setup_login_notification' => 'Flerfaktorautentisering er konfigurert, vennligst logg inn på nytt med denne metoden.',
];
\ No newline at end of file
'reset' => 'Nullstill',
'remove' => 'Fjern',
'add' => 'Legg til',
+ 'configure' => 'Konfigurer',
'fullscreen' => 'Fullskjerm',
- 'favourite' => 'Favourite',
- 'unfavourite' => 'Unfavourite',
- 'next' => 'Next',
- 'previous' => 'Previous',
+ 'favourite' => 'Favorisér',
+ 'unfavourite' => 'Avfavorisér',
+ 'next' => 'Neste',
+ 'previous' => 'Forrige',
// Sort Options
'sort_options' => 'Sorteringsalternativer',
'sort_ascending' => 'Stigende sortering',
'sort_descending' => 'Synkende sortering',
'sort_name' => 'Navn',
- 'sort_default' => 'Default',
+ 'sort_default' => 'Standard',
'sort_created_at' => 'Dato opprettet',
'sort_updated_at' => 'Dato oppdatert',
'no_activity' => 'Ingen aktivitet å vise',
'no_items' => 'Ingen ting å vise',
'back_to_top' => 'Hopp til toppen',
+ 'skip_to_main_content' => 'Gå til hovedinnhold',
'toggle_details' => 'Vis/skjul detaljer',
'toggle_thumbnails' => 'Vis/skjul miniatyrbilder',
'details' => 'Detaljer',
'breadcrumb' => 'Brødsmuler',
// Header
- 'header_menu_expand' => 'Expand Header Menu',
+ 'header_menu_expand' => 'Utvid toppmeny',
'profile_menu' => 'Profilmeny',
'view_profile' => 'Vis profil',
'edit_profile' => 'Endre Profile',
// Layout tabs
'tab_info' => 'Informasjon',
- 'tab_info_label' => 'Tab: Show Secondary Information',
+ 'tab_info_label' => 'Fane: Vis tilleggsinfo',
'tab_content' => 'Innhold',
- 'tab_content_label' => 'Tab: Show Primary Content',
+ 'tab_content_label' => 'Fane: Vis hovedinnhold',
// Email Content
'email_action_help' => 'Om du har problemer med å trykke på «:actionText»-knappen, bruk nettadressen under for å gå direkte dit:',
// Footer Link Options
// Not directly used but available for convenience to users.
- 'privacy_policy' => 'Privacy Policy',
- 'terms_of_service' => 'Terms of Service',
+ 'privacy_policy' => 'Personvernregler',
+ 'terms_of_service' => 'Bruksvilkår',
];
'images' => 'Bilder',
'my_recent_drafts' => 'Mine nylige utkast',
'my_recently_viewed' => 'Mine nylige visninger',
- 'my_most_viewed_favourites' => 'My Most Viewed Favourites',
- 'my_favourites' => 'My Favourites',
+ 'my_most_viewed_favourites' => 'Mine mest viste favoritter',
+ 'my_favourites' => 'Mine favoritter',
'no_pages_viewed' => 'Du har ikke sett på noen sider',
'no_pages_recently_created' => 'Ingen sider har nylig blitt opprettet',
'no_pages_recently_updated' => 'Ingen sider har nylig blitt oppdatert',
'export_html' => 'Nettside med alt',
'export_pdf' => 'PDF Fil',
'export_text' => 'Tekstfil',
+ 'export_md' => 'Markdownfil',
// Permissions and restrictions
'permissions' => 'Tilganger',
'search_permissions_set' => 'Tilganger er angitt',
'search_created_by_me' => 'Opprettet av meg',
'search_updated_by_me' => 'Oppdatert av meg',
- 'search_owned_by_me' => 'Owned by me',
+ 'search_owned_by_me' => 'Eid av meg',
'search_date_options' => 'Datoalternativer',
'search_updated_before' => 'Oppdatert før',
'search_updated_after' => 'Oppdatert etter',
'shelves_permissions' => 'Tilganger til hylla',
'shelves_permissions_updated' => 'Hyllas tilganger er oppdatert',
'shelves_permissions_active' => 'Hyllas tilganger er aktive',
+ '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' => 'Kopier tilganger til bøkene på hylla',
'shelves_copy_permissions' => 'Kopier tilganger',
'shelves_copy_permissions_explain' => 'Dette vil angi gjeldende tillatelsesinnstillinger for denne bokhyllen på alle bøkene som finnes på den. Før du aktiverer, må du forsikre deg om at endringer i tillatelsene til denne bokhyllen er lagret.',
'404_page_not_found' => 'Siden finnes ikke',
'sorry_page_not_found' => 'Beklager, siden du leter etter ble ikke funnet.',
'sorry_page_not_found_permission_warning' => 'Hvis du forventet at denne siden skulle eksistere, har du kanskje ikke tillatelse til å se den.',
- 'image_not_found' => 'Image Not Found',
- 'image_not_found_subtitle' => 'Sorry, The image file you were looking for could not be found.',
- 'image_not_found_details' => 'If you expected this image to exist it might have been deleted.',
+ 'image_not_found' => 'Bildet ble ikke funnet',
+ 'image_not_found_subtitle' => 'Beklager, bildefilen du ser etter ble ikke funnet.',
+ 'image_not_found_details' => 'Om du forventet at dette bildet skal eksistere, er det mulig det er slettet.',
'return_home' => 'Gå til hovedside',
'error_occurred' => 'En feil oppsto',
'app_down' => ':appName er nede for øyeblikket',
'app_homepage' => 'Applikasjonens hjemmeside',
'app_homepage_desc' => 'Velg en visning som skal vises på hjemmesiden i stedet for standardvisningen. Sidetillatelser ignoreres for utvalgte sider.',
'app_homepage_select' => 'Velg en side',
- 'app_footer_links' => 'Footer Links',
- 'app_footer_links_desc' => 'Add links to show within the site footer. These will be displayed at the bottom of most pages, including those that do not require login. You can use a label of "trans::<key>" to use system-defined translations. For example: Using "trans::common.privacy_policy" will provide the translated text "Privacy Policy" and "trans::common.terms_of_service" will provide the translated text "Terms of Service".',
- 'app_footer_links_label' => 'Link Label',
- 'app_footer_links_url' => 'Link URL',
- 'app_footer_links_add' => 'Add Footer Link',
+ 'app_footer_links' => 'Fotlenker',
+ 'app_footer_links_desc' => 'Legg til fotlenker i sidens fotområde. Disse vil vises nederst på de fleste sider, inkludert sider som ikke krever innlogging. Du kan bruke «trans::<key>» etiketter for system-definerte oversettelser. For eksempel: Bruk «trans::common.privacy_policy» for å vise teksten «Personvernregler» og «trans::common.terms_of_service» for å vise teksten «Bruksvilkår».',
+ 'app_footer_links_label' => 'Lenketekst',
+ 'app_footer_links_url' => 'Lenke',
+ 'app_footer_links_add' => 'Legg til fotlenke',
'app_disable_comments' => 'Deaktiver kommentarer',
'app_disable_comments_toggle' => 'Deaktiver kommentarer',
'app_disable_comments_desc' => 'Deaktiver kommentarer på tvers av alle sidene i applikasjonen. <br> Eksisterende kommentarer vises ikke.',
'recycle_bin' => 'Papirkurven',
'recycle_bin_desc' => 'Her kan du gjenopprette ting du har kastet i papirkurven eller velge å slette dem permanent fra systemet. Denne listen er ikke filtrert i motsetning til lignende lister i systemet hvor tilgangskontroll overholdes.',
'recycle_bin_deleted_item' => 'Kastet element',
+ 'recycle_bin_deleted_parent' => 'Overordnet',
'recycle_bin_deleted_by' => 'Kastet av',
'recycle_bin_deleted_at' => 'Kastet den',
'recycle_bin_permanently_delete' => 'Slett permanent',
'recycle_bin_restore_list' => 'Elementer som skal gjenopprettes',
'recycle_bin_restore_confirm' => 'Denne handlingen vil hente opp elementet fra papirkurven, inkludert underliggende innhold, til sin opprinnelige sted. Om den opprinnelige plassen har blitt slettet i mellomtiden og nå befinner seg i papirkurven, vil også dette bli hentet opp igjen.',
'recycle_bin_restore_deleted_parent' => 'Det overordnede elementet var også kastet i papirkurven. Disse elementene vil forbli kastet inntil det overordnede også hentes opp igjen.',
+ 'recycle_bin_restore_parent' => 'Gjenopprett overodnet',
'recycle_bin_destroy_notification' => 'Slettet :count elementer fra papirkurven.',
'recycle_bin_restore_notification' => 'Gjenopprettet :count elementer fra papirkurven.',
'audit_table_user' => 'Kontoholder',
'audit_table_event' => 'Hendelse',
'audit_table_related' => 'Relaterte elementer eller detaljer',
+ 'audit_table_ip' => 'IP Address',
'audit_table_date' => 'Aktivitetsdato',
'audit_date_from' => 'Datoperiode fra',
'audit_date_to' => 'Datoperiode til',
'role_details' => 'Rolledetaljer',
'role_name' => 'Rollenavn',
'role_desc' => 'Kort beskrivelse av rolle',
+ 'role_mfa_enforced' => 'Krever flerfaktorautentisering',
'role_external_auth_id' => 'Ekstern godkjennings-ID',
'role_system' => 'Systemtilganger',
'role_manage_users' => 'Behandle kontoer',
'role_manage_page_templates' => 'Behandle sidemaler',
'role_access_api' => 'Systemtilgang API',
'role_manage_settings' => 'Behandle applikasjonsinnstillinger',
+ 'role_export_content' => 'Export content',
'role_asset' => 'Eiendomstillatelser',
'roles_system_warning' => 'Vær oppmerksom på at tilgang til noen av de ovennevnte tre tillatelsene kan tillate en bruker å endre sine egne rettigheter eller rettighetene til andre i systemet. Bare tildel roller med disse tillatelsene til pålitelige brukere.',
'role_asset_desc' => 'Disse tillatelsene kontrollerer standard tilgang til eiendelene i systemet. Tillatelser til bøker, kapitler og sider overstyrer disse tillatelsene.',
'users_api_tokens_create' => 'Opprett nøkkel',
'users_api_tokens_expires' => 'Utløper',
'users_api_tokens_docs' => 'API-dokumentasjon',
+ 'users_mfa' => 'Flerfaktorautentisering',
+ 'users_mfa_desc' => 'Konfigurer flerfaktorautentisering som et ekstra lag med sikkerhet for din konto.',
+ 'users_mfa_x_methods' => ':count metode konfigurert|:count metoder konfigurert',
+ 'users_mfa_configure' => 'Konfigurer metoder',
// API Tokens
'user_api_token_create' => 'Opprett API-nøkkel',
'it' => 'Italian',
'ja' => '日本語',
'ko' => '한국어',
+ 'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nl' => 'Nederlands',
'nb' => 'Norsk (Bokmål)',
'alpha_dash' => ':attribute kan kunne inneholde bokstaver, tall, bindestreker eller understreker.',
'alpha_num' => ':attribute kan kun inneholde bokstaver og tall.',
'array' => ':attribute må være en liste.',
+ 'backup_codes' => 'Den angitte koden er ikke gyldig, eller er allerede benyttet.',
'before' => ':attribute må være en dato før :date.',
'between' => [
'numeric' => ':attribute må være mellom :min og :max.',
],
'string' => ':attribute må være en tekststreng.',
'timezone' => ':attribute må være en tidssone.',
+ 'totp' => 'Den angitte koden er ikke gyldig eller har utløpt.',
'unique' => ':attribute har allerede blitt tatt.',
'url' => ':attribute format er ugyldig.',
'uploaded' => 'kunne ikke lastes opp, tjeneren støtter ikke filer av denne størrelsen.',
'favourite_add_notification' => '":name" is toegevoegd aan je favorieten',
'favourite_remove_notification' => '":name" is verwijderd uit je favorieten',
+ // MFA
+ 'mfa_setup_method_notification' => 'Multi-factor method successfully configured',
+ 'mfa_remove_method_notification' => 'Multi-factor method successfully removed',
+
// Other
'commented_on' => 'reageerde op',
'permissions_update' => 'wijzigde permissies',
'user_invite_page_welcome' => 'Welkom bij :appName!',
'user_invite_page_text' => 'Om je account af te ronden en toegang te krijgen moet je een wachtwoord instellen dat gebruikt wordt om in te loggen op :appName bij toekomstige bezoeken.',
'user_invite_page_confirm_button' => 'Bevestig wachtwoord',
- 'user_invite_success' => 'Wachtwoord ingesteld, je hebt nu toegang tot :appName!'
+ 'user_invite_success' => 'Wachtwoord ingesteld, je hebt nu toegang tot :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_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.',
];
\ No newline at end of file
'reset' => 'Resetten',
'remove' => 'Verwijderen',
'add' => 'Toevoegen',
+ 'configure' => 'Configure',
'fullscreen' => 'Volledig scherm',
'favourite' => 'Favoriet',
'unfavourite' => 'Verwijderen uit favoriet',
'no_activity' => 'Geen activiteit om weer te geven',
'no_items' => 'Geen items beschikbaar',
'back_to_top' => 'Terug naar boven',
+ 'skip_to_main_content' => 'Direct naar de hoofdinhoud',
'toggle_details' => 'Details weergeven',
'toggle_thumbnails' => 'Thumbnails weergeven',
'details' => 'Details',
'export_html' => 'Ingesloten webbestand',
'export_pdf' => 'PDF bestand',
'export_text' => 'Normaal tekstbestand',
+ 'export_md' => 'Markdown bestand',
// Permissions and restrictions
'permissions' => 'Permissies',
'shelves_permissions' => 'Boekenplank permissies',
'shelves_permissions_updated' => 'Boekenplank permissies opgeslagen',
'shelves_permissions_active' => 'Boekenplank permissies actief',
+ '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' => 'Kopieer permissies naar boeken',
'shelves_copy_permissions' => 'Kopieer permissies',
'shelves_copy_permissions_explain' => 'Met deze actie worden de permissies van deze boekenplank gekopieërd naar alle boeken op de plank. Voordat deze actie wordt uitgevoerd, zorg dat de wijzigingen in de permissies van deze boekenplank zijn opgeslagen.',
'recycle_bin' => 'Prullenbak',
'recycle_bin_desc' => 'Hier kunt u items herstellen die zijn verwijderd of kiezen om ze permanent te verwijderen uit het systeem. Deze lijst is niet gefilterd, in tegenstelling tot vergelijkbare activiteitenlijsten in het systeem waar rechtenfilters worden toegepast.',
'recycle_bin_deleted_item' => 'Verwijderde Item',
+ 'recycle_bin_deleted_parent' => 'Bovenliggende',
'recycle_bin_deleted_by' => 'Verwijderd door',
'recycle_bin_deleted_at' => 'Verwijdert op',
'recycle_bin_permanently_delete' => 'Permanent verwijderen',
'recycle_bin_restore_list' => 'Items te herstellen',
'recycle_bin_restore_confirm' => 'Deze actie herstelt het verwijderde item, inclusief alle onderliggende elementen, op hun oorspronkelijke locatie. Als de oorspronkelijke locatie sindsdien is verwijderd en zich nu in de prullenbak bevindt, zal ook het bovenliggende item moeten worden hersteld.',
'recycle_bin_restore_deleted_parent' => 'De bovenliggende map van dit item is ook verwijderd. Deze zal worden verwijderd totdat het bovenliggende item ook is hersteld.',
+ 'recycle_bin_restore_parent' => 'Herstel bovenliggende',
'recycle_bin_destroy_notification' => 'Verwijderde totaal :count items uit de prullenbak.',
'recycle_bin_restore_notification' => 'Herstelde totaal :count items uit de prullenbak.',
'audit_table_user' => 'Gebruiker',
'audit_table_event' => 'Gebeurtenis',
'audit_table_related' => 'Gerelateerd Item of Detail',
+ 'audit_table_ip' => 'IP Address',
'audit_table_date' => 'Activiteit datum',
'audit_date_from' => 'Datum bereik vanaf',
'audit_date_to' => 'Datum bereik tot',
'role_details' => 'Rol Details',
'role_name' => 'Rolnaam',
'role_desc' => 'Korte beschrijving van de rol',
+ 'role_mfa_enforced' => 'Requires Multi-Factor Authentication',
'role_external_auth_id' => 'Externe authenticatie ID\'s',
'role_system' => 'Systeem Permissies',
'role_manage_users' => 'Gebruikers beheren',
'role_manage_page_templates' => 'Paginasjablonen beheren',
'role_access_api' => 'Ga naar systeem API',
'role_manage_settings' => 'Beheer app instellingen',
+ 'role_export_content' => 'Export content',
'role_asset' => 'Asset Permissies',
'roles_system_warning' => 'Wees ervan bewust dat toegang tot een van de bovengenoemde drie machtigingen een gebruiker in staat kan stellen zijn eigen privileges of de privileges van anderen in het systeem te wijzigen. Wijs alleen rollen toe met deze machtigingen aan vertrouwde gebruikers.',
'role_asset_desc' => 'Deze permissies bepalen de standaardtoegangsrechten. Permissies op boeken, hoofdstukken en pagina\'s overschrijven deze instelling.',
'users_api_tokens_create' => 'Token aanmaken',
'users_api_tokens_expires' => 'Verloopt',
'users_api_tokens_docs' => 'API Documentatie',
+ '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',
// API Tokens
'user_api_token_create' => 'API-token aanmaken',
'it' => 'Italian',
'ja' => '日本語',
'ko' => '한국어',
+ 'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nl' => 'Nederlands',
'nb' => 'Norsk (Bokmål)',
'alpha_dash' => ':attribute mag alleen letters, cijfers, streepjes en liggende streepjes bevatten.',
'alpha_num' => ':attribute mag alleen letters en nummers bevatten.',
'array' => ':attribute moet een reeks zijn.',
+ 'backup_codes' => 'The provided code is not valid or has already been used.',
'before' => ':attribute moet een datum zijn voor :date.',
'between' => [
'numeric' => ':attribute moet tussen de :min en :max zijn.',
],
'string' => ':attribute moet tekst zijn.',
'timezone' => ':attribute moet een geldige zone zijn.',
+ 'totp' => 'The provided code is not valid or has expired.',
'unique' => ':attribute is al in gebruik.',
'url' => ':attribute formaat is ongeldig.',
'uploaded' => 'Het bestand kon niet worden geüpload. De server accepteert mogelijk geen bestanden van deze grootte.',
'bookshelf_delete_notification' => 'Półka usunięta pomyślnie',
// Favourites
- 'favourite_add_notification' => '":name" has been added to your favourites',
- 'favourite_remove_notification' => '":name" has been removed from your favourites',
+ 'favourite_add_notification' => '":name" został dodany do Twoich ulubionych',
+ 'favourite_remove_notification' => '":name" został usunięty z ulubionych',
+
+ // MFA
+ 'mfa_setup_method_notification' => 'Metoda wieloskładnikowa została pomyślnie skonfigurowana',
+ 'mfa_remove_method_notification' => 'Metoda wieloskładnikowa pomyślnie usunięta',
// Other
'commented_on' => 'skomentował',
'user_invite_page_welcome' => 'Witaj w :appName!',
'user_invite_page_text' => 'Aby zakończyć tworzenie konta musisz ustawić hasło, które będzie używane do logowania do :appName w przyszłości.',
'user_invite_page_confirm_button' => 'Potwierdź hasło',
- 'user_invite_success' => 'Hasło zostało ustawione, teraz masz dostęp do :appName!'
+ 'user_invite_success' => 'Hasło zostało ustawione, teraz masz dostęp do :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_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.',
];
\ No newline at end of file
'reset' => 'Resetuj',
'remove' => 'Usuń',
'add' => 'Dodaj',
+ 'configure' => 'Configure',
'fullscreen' => 'Pełny ekran',
'favourite' => 'Favourite',
'unfavourite' => 'Unfavourite',
- 'next' => 'Next',
- 'previous' => 'Previous',
+ 'next' => 'Dalej',
+ 'previous' => 'Wstecz',
// Sort Options
'sort_options' => 'Opcje sortowania',
'sort_ascending' => 'Sortuj rosnąco',
'sort_descending' => 'Sortuj malejąco',
'sort_name' => 'Nazwa',
- 'sort_default' => 'Default',
+ 'sort_default' => 'Domyślne',
'sort_created_at' => 'Data utworzenia',
'sort_updated_at' => 'Data aktualizacji',
'no_activity' => 'Brak aktywności do wyświetlenia',
'no_items' => 'Brak elementów do wyświetlenia',
'back_to_top' => 'Powrót na górę',
+ 'skip_to_main_content' => 'Przejdź do treści głównej',
'toggle_details' => 'Włącz/wyłącz szczegóły',
'toggle_thumbnails' => 'Włącz/wyłącz miniatury',
'details' => 'Szczegóły',
'export_html' => 'Plik HTML',
'export_pdf' => 'Plik PDF',
'export_text' => 'Plik tekstowy',
+ 'export_md' => 'Markdown File',
// Permissions and restrictions
'permissions' => 'Uprawnienia',
'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' => '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' => 'Skopiuj uprawnienia do książek',
'shelves_copy_permissions' => 'Skopiuj uprawnienia',
'shelves_copy_permissions_explain' => 'To spowoduje zastosowanie obecnych ustawień uprawnień dla tej półki do wszystkich książek w niej zawartych. Przed aktywacją upewnij się, że wszelkie zmiany w uprawnieniach do tej półki zostały zapisane.',
'404_page_not_found' => 'Strona nie została znaleziona',
'sorry_page_not_found' => 'Przepraszamy, ale strona której szukasz nie została znaleziona.',
'sorry_page_not_found_permission_warning' => 'Jeśli spodziewałeś się, że ta strona istnieje, prawdopodobnie nie masz uprawnień do jej wyświetlenia.',
- 'image_not_found' => 'Image Not Found',
- 'image_not_found_subtitle' => 'Sorry, The image file you were looking for could not be found.',
- 'image_not_found_details' => 'If you expected this image to exist it might have been deleted.',
+ 'image_not_found' => 'Nie znaleziono obrazu',
+ 'image_not_found_subtitle' => 'Przepraszamy, ale obraz którego szukasz nie został znaleziony.',
+ 'image_not_found_details' => 'Jeśli spodziewałeś się, że ten obraz istnieje, mógł on zostać usunięty.',
'return_home' => 'Powrót do strony głównej',
'error_occurred' => 'Wystąpił błąd',
'app_down' => ':appName jest aktualnie wyłączona',
'app_features_security' => 'Funkcje i bezpieczeństwo',
'app_name' => 'Nazwa aplikacji',
'app_name_desc' => 'Ta nazwa jest wyświetlana w nagłówku i e-mailach.',
- 'app_name_header' => 'Pokazać nazwę aplikacji w nagłówku?',
+ 'app_name_header' => 'Pokaż nazwę aplikacji w nagłówku',
'app_public_access' => 'Dostęp publiczny',
'app_public_access_desc' => 'Włączenie tej opcji umożliwi niezalogowanym odwiedzającym dostęp do treści w Twojej instancji BookStack.',
'app_public_access_desc_guest' => 'Dostęp dla niezalogowanych odwiedzających jest dostępny poprzez użytkownika "Guest".',
'reg_settings' => 'Ustawienia rejestracji',
'reg_enable' => 'Włącz rejestrację',
'reg_enable_toggle' => 'Włącz rejestrację',
- 'reg_enable_desc' => 'Kiedy rejestracja jest włączona użytkownicy mogą się rejestrować. Po rejestracji otrzymują jedną domyślną rolę użytkownika.',
+ 'reg_enable_desc' => 'Po włączeniu rejestracji użytkownicy ci będą mogli się samodzielnie zarejestrować i otrzymają domyślną rolę.',
'reg_default_role' => 'Domyślna rola użytkownika po rejestracji',
'reg_enable_external_warning' => 'Powyższa opcja jest ignorowana, gdy zewnętrzne uwierzytelnianie LDAP lub SAML jest aktywne. Konta użytkowników dla nieistniejących użytkowników zostaną automatycznie utworzone, jeśli uwierzytelnianie za pomocą systemu zewnętrznego zakończy się sukcesem.',
'reg_email_confirmation' => 'Potwierdzenie adresu email',
// Recycle Bin
'recycle_bin' => 'Kosz',
- 'recycle_bin_desc' => 'Here you can restore items that have been deleted or choose to permanently remove them from the system. This list is unfiltered unlike similar activity lists in the system where permission filters are applied.',
+ 'recycle_bin_desc' => 'Tutaj możesz przywrócić elementy, które zostały usunięte lub usunąć je z systemu. Ta lista jest niefiltrowana w odróżnieniu od podobnych list aktywności w systemie, w którym stosowane są filtry uprawnień.',
'recycle_bin_deleted_item' => 'Usunięta pozycja',
+ 'recycle_bin_deleted_parent' => 'Parent',
'recycle_bin_deleted_by' => 'Usunięty przez',
'recycle_bin_deleted_at' => 'Czas usunięcia',
'recycle_bin_permanently_delete' => 'Usuń trwale',
'recycle_bin_restore' => 'Przywróć',
'recycle_bin_contents_empty' => 'Kosz jest pusty',
'recycle_bin_empty' => 'Opróżnij kosz',
- 'recycle_bin_empty_confirm' => 'This will permanently destroy all items in the recycle bin including content contained within each item. Are you sure you want to empty the recycle bin?',
+ 'recycle_bin_empty_confirm' => 'To na stałe zniszczy wszystkie przedmioty w koszu, w tym zawartość w każdym elemencie. Czy na pewno chcesz opróżnić kosz?',
'recycle_bin_destroy_confirm' => 'This action will permanently delete this item, along with any child elements listed below, from the system and you will not be able to restore this content. Are you sure you want to permanently delete this item?',
- 'recycle_bin_destroy_list' => 'Items to be Destroyed',
+ 'recycle_bin_destroy_list' => 'Elementy do usunięcia',
'recycle_bin_restore_list' => 'Elementy do przywrócenia',
'recycle_bin_restore_confirm' => 'This action will restore the deleted item, including any child elements, to their original location. If the original location has since been deleted, and is now in the recycle bin, the parent item will also need to be restored.',
'recycle_bin_restore_deleted_parent' => 'The parent of this item has also been deleted. These will remain deleted until that parent is also restored.',
- 'recycle_bin_destroy_notification' => 'Deleted :count total items from the recycle bin.',
- 'recycle_bin_restore_notification' => 'Restored :count total items from the recycle bin.',
+ 'recycle_bin_restore_parent' => 'Restore Parent',
+ 'recycle_bin_destroy_notification' => 'Usunięto :count przedmiotów z kosza.',
+ 'recycle_bin_restore_notification' => 'Przywrócono :count przedmiotów z kosza.',
// Audit Log
'audit' => 'Dziennik audytu',
'audit_table_user' => 'Użytkownik',
'audit_table_event' => 'Wydarzenie',
'audit_table_related' => 'Powiązany element lub szczegóły',
+ 'audit_table_ip' => 'Adres IP',
'audit_table_date' => 'Data Aktywności',
'audit_date_from' => 'Zakres dat od',
'audit_date_to' => 'Zakres dat do',
'role_details' => 'Szczegóły roli',
'role_name' => 'Nazwa roli',
'role_desc' => 'Krótki opis roli',
+ 'role_mfa_enforced' => 'Requires Multi-Factor Authentication',
'role_external_auth_id' => 'Zewnętrzne identyfikatory uwierzytelniania',
'role_system' => 'Uprawnienia systemowe',
'role_manage_users' => 'Zarządzanie użytkownikami',
'role_manage_page_templates' => 'Zarządzaj szablonami stron',
'role_access_api' => 'Dostęp do systemowego API',
'role_manage_settings' => 'Zarządzanie ustawieniami aplikacji',
+ 'role_export_content' => 'Export content',
'role_asset' => 'Zarządzanie zasobami',
'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.',
'users_api_tokens_create' => 'Utwórz token',
'users_api_tokens_expires' => 'Wygasa',
'users_api_tokens_docs' => 'Dokumentacja API',
+ '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',
// API Tokens
'user_api_token_create' => 'Utwórz klucz API',
'it' => 'Italian',
'ja' => '日本語',
'ko' => '한국어',
+ 'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nl' => 'Nederlands',
'nb' => 'Norsk (Bokmål)',
'alpha_dash' => ':attribute może zawierać wyłącznie litery, cyfry i myślniki.',
'alpha_num' => ':attribute może zawierać wyłącznie litery i cyfry.',
'array' => ':attribute musi być tablicą.',
+ 'backup_codes' => 'The provided code is not valid or has already been used.',
'before' => ':attribute musi być datą poprzedzającą :date.',
'between' => [
'numeric' => ':attribute musi zawierać się w przedziale od :min do :max.',
'integer' => ':attribute musi być liczbą całkowitą.',
'ip' => ':attribute musi być prawidłowym adresem IP.',
'ipv4' => ':attribute musi być prawidłowym adresem IPv4.',
- 'ipv6' => ':attribute musi być prawidłowym adresem IPv6.',
+ 'ipv6' => ':attribute musi być prawidłowym adresem IPv6.',
'json' => ':attribute musi być prawidłowym ciągiem JSON.',
'lt' => [
'numeric' => ':attribute musi być mniejszy niż :value.',
],
'string' => ':attribute musi być ciągiem znaków.',
'timezone' => ':attribute musi być prawidłową strefą czasową.',
+ 'totp' => 'The provided code is not valid or has expired.',
'unique' => ':attribute zostało już zajęte.',
'url' => 'Format :attribute jest nieprawidłowy.',
'uploaded' => 'Plik nie może zostać wysłany. Serwer nie akceptuje plików o takim rozmiarze.',
'bookshelf_delete_notification' => 'Estante eliminada com sucesso',
// Favourites
- 'favourite_add_notification' => '":name" has been added to your favourites',
- 'favourite_remove_notification' => '":name" has been removed from your favourites',
+ 'favourite_add_notification' => '":name" foi adicionado aos seus favoritos',
+ 'favourite_remove_notification' => '":name" foi removido dos seus favoritos',
+
+ // MFA
+ 'mfa_setup_method_notification' => 'Multi-factor method successfully configured',
+ 'mfa_remove_method_notification' => 'Multi-factor method successfully removed',
// Other
'commented_on' => 'comentado a',
'user_invite_page_welcome' => 'Bem-vindo(a) a :appName!',
'user_invite_page_text' => 'Para finalizar a sua conta e obter acesso, precisa de definir uma senha que será utilizada para efetuar login em :appName em visitas futuras.',
'user_invite_page_confirm_button' => 'Confirmar Palavra-Passe',
- 'user_invite_success' => 'Palavra-passe definida, tem agora acesso a :appName!'
+ 'user_invite_success' => 'Palavra-passe definida, tem agora acesso a :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_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.',
];
\ No newline at end of file
'reset' => 'Redefinir',
'remove' => 'Remover',
'add' => 'Adicionar',
+ 'configure' => 'Configure',
'fullscreen' => 'Ecrã completo',
- 'favourite' => 'Favourite',
- 'unfavourite' => 'Unfavourite',
- 'next' => 'Next',
- 'previous' => 'Previous',
+ 'favourite' => 'Favorito',
+ 'unfavourite' => 'Retirar Favorito',
+ 'next' => 'Próximo',
+ 'previous' => 'Anterior',
// Sort Options
'sort_options' => 'Opções de Ordenação',
'no_activity' => 'Nenhuma atividade a mostrar',
'no_items' => 'Nenhum item disponível',
'back_to_top' => 'Voltar ao topo',
+ 'skip_to_main_content' => 'Avançar para o conteúdo principal',
'toggle_details' => 'Alternar Detalhes',
'toggle_thumbnails' => 'Alternar Miniaturas',
'details' => 'Detalhes',
'images' => 'Imagens',
'my_recent_drafts' => 'Os Meus Rascunhos Recentes',
'my_recently_viewed' => 'Visualizados Recentemente Por Mim',
- 'my_most_viewed_favourites' => 'My Most Viewed Favourites',
- 'my_favourites' => 'My Favourites',
+ 'my_most_viewed_favourites' => 'Os Meus Favoritos Mais Visualizados',
+ 'my_favourites' => 'Os Meus Favoritos',
'no_pages_viewed' => 'Você não viu nenhuma página',
'no_pages_recently_created' => 'Nenhuma página foi recentemente criada',
'no_pages_recently_updated' => 'Nenhuma página foi recentemente atualizada',
'export_html' => 'Arquivo Web contido',
'export_pdf' => 'Arquivo PDF',
'export_text' => 'Arquivo Texto',
+ 'export_md' => 'Ficheiro Markdown',
// Permissions and restrictions
'permissions' => 'Permissões',
'shelves_permissions' => 'Permissões da Estante',
'shelves_permissions_updated' => 'Permissões da Estante de Livros Atualizada',
'shelves_permissions_active' => 'Permissões da Estante de Livros Ativas',
+ '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' => 'Copiar Permissões para Livros',
'shelves_copy_permissions' => 'Copiar Permissões',
'shelves_copy_permissions_explain' => 'Isto aplicará as configurações de permissões atuais desta estante a todos os livros nela contidos. Antes de ativar, assegure-se de que quaisquer alterações nas permissões desta estante foram guardadas.',
'recycle_bin' => 'Reciclagem',
'recycle_bin_desc' => 'Aqui pode restaurar itens que foram eliminados ou eliminá-los permanentemente do sistema. Esta lista não é filtrada diferentemente de listas de atividades parecidas no sistema onde filtros de permissão são aplicados.',
'recycle_bin_deleted_item' => 'Item eliminado',
+ 'recycle_bin_deleted_parent' => 'Parente',
'recycle_bin_deleted_by' => 'Eliminado por',
'recycle_bin_deleted_at' => 'Data de Eliminação',
'recycle_bin_permanently_delete' => 'Eliminar permanentemente',
'recycle_bin_restore_list' => 'Itens a serem Restaurados',
'recycle_bin_restore_confirm' => 'Esta ação irá restaurar o item excluído, inclusive quaisquer elementos filhos, para o seu local original. Se a localização original tiver, entretanto, sido eliminada e estiver agora na reciclagem, o item pai também precisará de ser restaurado.',
'recycle_bin_restore_deleted_parent' => 'O parente deste item foi também eliminado. Estes permanecerão eliminados até que o parente seja também restaurado.',
+ 'recycle_bin_restore_parent' => 'Restaurar Parente',
'recycle_bin_destroy_notification' => 'Eliminados no total :count itens da lixeira.',
'recycle_bin_restore_notification' => 'Restaurados no total :count itens da reciclagem.',
'audit_table_user' => 'Utilizador',
'audit_table_event' => 'Evento',
'audit_table_related' => 'Item ou Detalhe Relacionado',
+ 'audit_table_ip' => 'IP Address',
'audit_table_date' => 'Data da Atividade',
'audit_date_from' => 'Intervalo De',
'audit_date_to' => 'Intervalo Até',
'role_details' => 'Detalhes do Cargo',
'role_name' => 'Nome do Cargo',
'role_desc' => 'Breve Descrição do Cargo',
+ 'role_mfa_enforced' => 'Requires Multi-Factor Authentication',
'role_external_auth_id' => 'IDs de Autenticação Externa',
'role_system' => 'Permissões do Sistema',
'role_manage_users' => 'Gerir utilizadores',
'role_manage_page_templates' => 'Gerir modelos de página',
'role_access_api' => 'Aceder à API do sistema',
'role_manage_settings' => 'Gerir as configurações da aplicação',
+ 'role_export_content' => 'Export content',
'role_asset' => 'Permissões de Ativos',
'roles_system_warning' => 'Esteja ciente de que o acesso a qualquer uma das três permissões acima pode permitir que um utilizador altere os seus próprios privilégios ou privilégios de outros no sistema. Apenas atribua cargos com essas permissões a utilizadores de confiança.',
'role_asset_desc' => 'Estas 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 estas permissões.',
'users_api_tokens_create' => 'Criar Token',
'users_api_tokens_expires' => 'Expira',
'users_api_tokens_docs' => 'Documentação da API',
+ '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',
// API Tokens
'user_api_token_create' => 'Criar Token de API',
'it' => 'Italian',
'ja' => '日本語',
'ko' => '한국어',
+ 'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nl' => 'Nederlands',
'nb' => 'Norsk (Bokmål)',
'alpha_dash' => 'O campo :attribute deve conter apenas letras, números, traços e sublinhado.',
'alpha_num' => 'O campo :attribute deve conter apenas letras e números.',
'array' => 'O campo :attribute deve ser uma lista(array).',
+ 'backup_codes' => 'The provided code is not valid or has already been used.',
'before' => 'O campo :attribute deve ser uma data anterior à data :date.',
'between' => [
'numeric' => 'O campo :attribute deve estar entre :min e :max.',
],
'string' => 'O campo :attribute deve ser uma string.',
'timezone' => 'O campo :attribute deve conter uma timezone válida.',
+ 'totp' => 'The provided code is not valid or has expired.',
'unique' => 'Já existe um campo/dado de nome :attribute.',
'url' => 'O formato da URL :attribute é inválido.',
'uploaded' => 'O arquivo não pôde ser carregado. O servidor pode não aceitar arquivos deste tamanho.',
'favourite_add_notification' => '":name" has been added to your favourites',
'favourite_remove_notification' => '":name" has been removed from your favourites',
+ // MFA
+ 'mfa_setup_method_notification' => 'Multi-factor method successfully configured',
+ 'mfa_remove_method_notification' => 'Multi-factor method successfully removed',
+
// Other
'commented_on' => 'comentou em',
'permissions_update' => 'atualizou permissões',
'user_invite_page_welcome' => 'Bem-vindo(a) a :appName!',
'user_invite_page_text' => 'Para finalizar sua conta e obter acesso, você precisa definir uma senha que será usada para efetuar login em :appName em futuras visitas.',
'user_invite_page_confirm_button' => 'Confirmar Senha',
- 'user_invite_success' => 'Senha definida, você agora tem acesso a :appName!'
+ 'user_invite_success' => 'Senha definida, você agora tem acesso a :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_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.',
];
\ No newline at end of file
'reset' => 'Redefinir',
'remove' => 'Remover',
'add' => 'Adicionar',
+ 'configure' => 'Configure',
'fullscreen' => 'Tela cheia',
- 'favourite' => 'Favourite',
- 'unfavourite' => 'Unfavourite',
- 'next' => 'Next',
- 'previous' => 'Previous',
+ 'favourite' => 'Favoritos',
+ 'unfavourite' => 'Remover dos Favoritos',
+ 'next' => 'Seguinte',
+ 'previous' => 'Anterior',
// Sort Options
'sort_options' => 'Opções de Ordenação',
'no_activity' => 'Nenhuma atividade a mostrar',
'no_items' => 'Nenhum item disponível',
'back_to_top' => 'Voltar ao topo',
+ 'skip_to_main_content' => 'Ir para o conteúdo principal',
'toggle_details' => 'Alternar Detalhes',
'toggle_thumbnails' => 'Alternar Miniaturas',
'details' => 'Detalhes',
'export_html' => 'Arquivo Web Contained',
'export_pdf' => 'Arquivo PDF',
'export_text' => 'Arquivo Texto',
+ 'export_md' => 'Markdown File',
// Permissions and restrictions
'permissions' => 'Permissões',
'shelves_permissions' => 'Permissões da Prateleira',
'shelves_permissions_updated' => 'Permissões da Prateleira Atualizadas',
'shelves_permissions_active' => 'Permissões da Prateleira Ativas',
+ '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' => 'Copiar Permissões para Livros',
'shelves_copy_permissions' => 'Copiar Permissões',
'shelves_copy_permissions_explain' => 'Isto aplicará as configurações de permissões atuais desta prateleira a todos os livros contidos nela. Antes de ativar, assegure-se de que quaisquer alterações nas permissões desta prateleira tenham sido salvas.',
'recycle_bin' => 'Lixeira',
'recycle_bin_desc' => 'Aqui você pode restaurar itens que foram excluídos ou escolher removê-los permanentemente do sistema. Esta lista não é filtrada diferentemente de listas de atividades similares no sistema onde filtros de permissão são aplicados.',
'recycle_bin_deleted_item' => 'Item excluído',
+ 'recycle_bin_deleted_parent' => 'Parent',
'recycle_bin_deleted_by' => 'Excluído por',
'recycle_bin_deleted_at' => 'Momento de Exclusão',
'recycle_bin_permanently_delete' => 'Excluir permanentemente',
'recycle_bin_restore_list' => 'Itens a serem restaurados',
'recycle_bin_restore_confirm' => 'Esta ação irá restaurar o item excluído, inclusive quaisquer elementos filhos, para seu local original. Se a localização original tiver, entretanto, sido eliminada e estiver agora na lixeira, o item pai também precisará ser restaurado.',
'recycle_bin_restore_deleted_parent' => 'The parent of this item has also been deleted. These will remain deleted until that parent is also restored.',
+ 'recycle_bin_restore_parent' => 'Restore Parent',
'recycle_bin_destroy_notification' => 'Deleted :count total items from the recycle bin.',
'recycle_bin_restore_notification' => 'Restored :count total items from the recycle bin.',
'audit_table_user' => 'Usuário',
'audit_table_event' => 'Evento',
'audit_table_related' => 'Related Item or Detail',
+ 'audit_table_ip' => 'IP Address',
'audit_table_date' => 'Data da Atividade',
'audit_date_from' => 'Date Range From',
'audit_date_to' => 'Date Range To',
'role_details' => 'Detalhes do Cargo',
'role_name' => 'Nome do Cargo',
'role_desc' => 'Breve Descrição do Cargo',
+ 'role_mfa_enforced' => 'Requires Multi-Factor Authentication',
'role_external_auth_id' => 'IDs de Autenticação Externa',
'role_system' => 'Permissões do Sistema',
'role_manage_users' => 'Gerenciar usuários',
'role_manage_page_templates' => 'Gerenciar modelos de página',
'role_access_api' => 'Acessar API do sistema',
'role_manage_settings' => 'Gerenciar configurações da aplicação',
+ 'role_export_content' => 'Export content',
'role_asset' => 'Permissões de Ativos',
'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.',
'users_api_tokens_create' => 'Criar Token',
'users_api_tokens_expires' => 'Expira',
'users_api_tokens_docs' => 'Documentação da API',
+ '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',
// API Tokens
'user_api_token_create' => 'Criar Token de API',
'it' => 'Italian',
'ja' => '日本語',
'ko' => '한국어',
+ 'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nl' => 'Nederlands',
'nb' => 'Norsk (Bokmål)',
'alpha_dash' => 'O campo :attribute deve conter apenas letras, números, traços e underlines.',
'alpha_num' => 'O campo :attribute deve conter apenas letras e números.',
'array' => 'O campo :attribute deve ser uma array.',
+ 'backup_codes' => 'The provided code is not valid or has already been used.',
'before' => 'O campo :attribute deve ser uma data anterior à data :date.',
'between' => [
'numeric' => 'O campo :attribute deve estar entre :min e :max.',
],
'string' => 'O campo :attribute deve ser uma string.',
'timezone' => 'O campo :attribute deve conter uma timezone válida.',
+ 'totp' => 'The provided code is not valid or has expired.',
'unique' => 'Já existe um campo/dado de nome :attribute.',
'url' => 'O formato da URL :attribute é inválido.',
'uploaded' => 'O arquivo não pôde ser carregado. O servidor pode não aceitar arquivos deste tamanho.',
// Favourites
'favourite_add_notification' => '":name" добавлено в избранное',
- 'favourite_remove_notification' => ':name" удалено из избранного',
+ 'favourite_remove_notification' => '":name" удалено из избранного',
+
+ // MFA
+ 'mfa_setup_method_notification' => 'Двухфакторный метод авторизации успешно настроен',
+ 'mfa_remove_method_notification' => 'Двухфакторный метод авторизации успешно удален',
// Other
'commented_on' => 'прокомментировал',
'user_invite_page_welcome' => 'Добро пожаловать в :appName!',
'user_invite_page_text' => 'Завершите настройку аккаунта, установите пароль для дальнейшего входа в :appName.',
'user_invite_page_confirm_button' => 'Подтвердите пароль',
- 'user_invite_success' => 'Пароль установлен, теперь у вас есть доступ к :appName!'
+ 'user_invite_success' => 'Пароль установлен, теперь у вас есть доступ к :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' => 'Двухфакторный метод настроен, пожалуйста, войдите снова, используя сконфигурированный метод.',
];
\ No newline at end of file
'reset' => 'Сбросить',
'remove' => 'Удалить',
'add' => 'Добавить',
+ 'configure' => 'Configure',
'fullscreen' => 'На весь экран',
'favourite' => 'Избранное',
'unfavourite' => 'Убрать из избранного',
'no_activity' => 'Нет действий для просмотра',
'no_items' => 'Нет доступных элементов',
'back_to_top' => 'Наверх',
+ 'skip_to_main_content' => 'Перейти к основному контенту',
'toggle_details' => 'Подробности',
'toggle_thumbnails' => 'Миниатюры',
'details' => 'Детали',
'export_html' => 'Веб файл',
'export_pdf' => 'PDF файл',
'export_text' => 'Текстовый файл',
+ 'export_md' => 'Markdown File',
// Permissions and restrictions
'permissions' => 'Разрешения',
'shelves_permissions' => 'Доступы к книжной полке',
'shelves_permissions_updated' => 'Доступы к книжной полке обновлены',
'shelves_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' => 'Это применит текущие настройки доступов этой книжной полки ко всем книгам, содержащимся внутри. Перед активацией убедитесь, что все изменения в доступах этой книжной полки сохранены.',
'recycle_bin' => 'Корзина',
'recycle_bin_desc' => 'Здесь вы можете восстановить удаленные элементы или навсегда удалить их из системы. Этот список не отфильтрован в отличие от аналогичных списков действий в системе, где применяются фильтры.',
'recycle_bin_deleted_item' => 'Удаленный элемент',
+ 'recycle_bin_deleted_parent' => 'Parent',
'recycle_bin_deleted_by' => 'Удалён',
'recycle_bin_deleted_at' => 'Время удаления',
'recycle_bin_permanently_delete' => 'Удалить навсегда',
'recycle_bin_restore_list' => 'Элементы для восстановления',
'recycle_bin_restore_confirm' => 'Это действие восстановит удаленный элемент, включая дочерние, в исходное место. Если исходное место было удалено и теперь находится в корзине, родительский элемент также необходимо будет восстановить.',
'recycle_bin_restore_deleted_parent' => 'Родитель этого элемента также был удален. Элементы будут удалены до тех пор, пока этот родитель не будет восстановлен.',
+ 'recycle_bin_restore_parent' => 'Restore Parent',
'recycle_bin_destroy_notification' => 'Удалено :count элементов из корзины.',
'recycle_bin_restore_notification' => 'Восстановлено :count элементов из корзины',
'audit_table_user' => 'Пользователь',
'audit_table_event' => 'Событие',
'audit_table_related' => 'Связанный элемент',
+ 'audit_table_ip' => 'IP-адрес',
'audit_table_date' => 'Дата действия',
'audit_date_from' => 'Диапазон даты от',
'audit_date_to' => 'Диапазон даты до',
'role_details' => 'Детали роли',
'role_name' => 'Название роли',
'role_desc' => 'Краткое описание роли',
+ 'role_mfa_enforced' => 'Requires Multi-Factor Authentication',
'role_external_auth_id' => 'Внешние ID авторизации',
'role_system' => 'Системные разрешения',
'role_manage_users' => 'Управление пользователями',
'role_manage_page_templates' => 'Управление шаблонами страниц',
'role_access_api' => 'Доступ к системному API',
'role_manage_settings' => 'Управление настройками приложения',
+ 'role_export_content' => 'Export content',
'role_asset' => 'Права доступа к материалам',
'roles_system_warning' => 'Имейте в виду, что доступ к любому из указанных выше трех разрешений может позволить пользователю изменить свои собственные привилегии или привилегии других пользователей системы. Назначать роли с этими правами можно только доверенным пользователям.',
'role_asset_desc' => 'Эти разрешения контролируют доступ по умолчанию к параметрам внутри системы. Разрешения на книги, главы и страницы перезапишут эти разрешения.',
'users_api_tokens_create' => 'Создать токен',
'users_api_tokens_expires' => 'Истекает',
'users_api_tokens_docs' => 'Документация',
+ 'users_mfa' => 'Двухфакторная аутентификация',
+ 'users_mfa_desc' => 'Двухфакторная аутентификация повышает степень безопасности вашей учетной записи.',
+ 'users_mfa_x_methods' => ':count method configured|:count methods configured',
+ 'users_mfa_configure' => 'Настройка методов',
// API Tokens
'user_api_token_create' => 'Создать токен',
'it' => 'Italian',
'ja' => '日本語',
'ko' => '한국어',
+ 'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nl' => 'Nederlands',
'nb' => 'Norsk (Bokmål)',
'alpha_dash' => ':attribute может содержать только буквы, цифры и тире.',
'alpha_num' => ':attribute должен содержать только буквы и цифры.',
'array' => ':attribute должен быть массивом.',
+ 'backup_codes' => 'Указанный код недействителен или уже использован.',
'before' => ':attribute дата должна быть до :date.',
'between' => [
'numeric' => ':attribute должен быть между :min и :max.',
],
'string' => ':attribute должен быть строкой.',
'timezone' => ':attribute должен быть корректным часовым поясом.',
+ 'totp' => 'Указанный код недействителен или истек.',
'unique' => ':attribute уже есть.',
'url' => 'Формат :attribute некорректен.',
'uploaded' => 'Не удалось загрузить файл. Сервер не может принимать файлы такого размера.',
// Pages
'page_create' => 'vytvoril(a) stránku',
'page_create_notification' => 'Stránka úspešne vytvorená',
- 'page_update' => 'aktualizoval stránku',
+ 'page_update' => 'aktualizoval(a) stránku',
'page_update_notification' => 'Stránka úspešne aktualizovaná',
'page_delete' => 'odstránil(a) stránku',
'page_delete_notification' => 'Stránka úspešne odstránená',
'bookshelf_delete_notification' => 'Knižnica úspešne odstránená',
// Favourites
- 'favourite_add_notification' => '":name" has been added to your favourites',
- 'favourite_remove_notification' => '":name" has been removed from your favourites',
+ 'favourite_add_notification' => '":name" bol pridaný medzi obľúbené',
+ 'favourite_remove_notification' => '":name" bol odstránený z obľúbených',
+
+ // MFA
+ 'mfa_setup_method_notification' => 'Viacúrovňový spôsob overenia úspešne nastavený',
+ 'mfa_remove_method_notification' => 'Viacúrovňový spôsob overenia úspešne odstránený',
// Other
'commented_on' => 'komentoval(a)',
'user_invite_page_welcome' => 'Vitajte v :appName!',
'user_invite_page_text' => 'Ak chcete dokončiť svoj účet a získať prístup, musíte nastaviť heslo, ktoré sa použije na prihlásenie do aplikácie :appName pri budúcich návštevách.',
'user_invite_page_confirm_button' => 'Potvrdiť heslo',
- 'user_invite_success' => 'Heslo bolo nastavené, teraz máte prístup k :appName!'
+ 'user_invite_success' => 'Heslo bolo nastavené, teraz máte prístup k :appName!',
+
+ // Multi-factor Authentication
+ 'mfa_setup' => 'Nastaviť viacúrovňové prihlasovanie',
+ 'mfa_setup_desc' => 'Pre vyššiu úroveň bezpečnosti si nastavte viacúrovňové prihlasovanie.',
+ 'mfa_setup_configured' => 'Už nastavené',
+ 'mfa_setup_reconfigure' => 'Znovunastavenie',
+ 'mfa_setup_remove_confirmation' => 'Ste si istý, že chcete odstrániť tento spôsob viacúrovňového overenia?',
+ 'mfa_setup_action' => 'Nastaveine',
+ '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' => 'Mobilná aplikácia',
+ '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' => 'Záložné kódy',
+ 'mfa_option_backup_codes_desc' => 'Bezpečne uložte jednorázové záložné kódy pre overenie vačej identity.',
+ 'mfa_gen_confirm_and_enable' => 'Potvrdiť a zapnúť',
+ 'mfa_gen_backup_codes_title' => 'Nastavenie záložných kódov',
+ 'mfa_gen_backup_codes_desc' => 'Uložte si tieto kódy na bezpečné miesto. Jeden z kódov budete môcť použiť ako druhý faktor overenia identiy na prihlásenie sa.',
+ 'mfa_gen_backup_codes_download' => 'Stiahnuť kódy',
+ 'mfa_gen_backup_codes_usage_warning' => 'Každý kód môže byť použitý len jeden krát',
+ 'mfa_gen_totp_title' => 'Nastavenie mobilnej aplikácie',
+ 'mfa_gen_totp_desc' => 'Pre používanie viacúrovňového prihlasovania budete potrebovať mobilnú aplikáciu, ktorá podporuje TOPS ako napríklad Google Authenticator, Authy alebo Microsoft Authenticator.',
+ 'mfa_gen_totp_scan' => 'Naskenujte 1R k\'d pomocou vašej mobilnej aplikácie.',
+ 'mfa_gen_totp_verify_setup' => 'Overiť nastavenie',
+ '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' => 'Sem vložte kód vygenerovaný vašou mobilnou aplikáciou',
+ 'mfa_verify_access' => 'Overiť prístup',
+ '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' => 'Žiadny spôsob nebol nastavený',
+ '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' => 'Overiť pomocou mobilnej aplikácie',
+ 'mfa_verify_use_backup_codes' => 'Overiť pomocou záložného kódu',
+ 'mfa_verify_backup_code' => 'Záložný kód',
+ 'mfa_verify_backup_code_desc' => 'Enter one of your remaining backup codes below:',
+ 'mfa_verify_backup_code_enter_here' => 'Zadajte záložný kód',
+ 'mfa_verify_totp_desc' => 'Zadajte kód vygenerovaný vašou mobilnou aplikáciou:',
+ 'mfa_setup_login_notification' => 'Multi-factor method configured, Please now login again using the configured method.',
];
\ No newline at end of file
'reset' => 'Resetovať',
'remove' => 'Odstrániť',
'add' => 'Pridať',
+ 'configure' => 'Konfigurácia',
'fullscreen' => 'Celá obrazovka',
- 'favourite' => 'Favourite',
- 'unfavourite' => 'Unfavourite',
- 'next' => 'Next',
- 'previous' => 'Previous',
+ 'favourite' => 'Pridať do obľúbených',
+ 'unfavourite' => 'Odstrániť z obľúbených',
+ 'next' => 'Ďalej',
+ 'previous' => 'Späť',
// Sort Options
'sort_options' => 'Možnosti triedenia',
'sort_ascending' => 'Zoradiť vzostupne',
'sort_descending' => 'Zoradiť zostupne',
'sort_name' => 'Meno',
- 'sort_default' => 'Default',
+ 'sort_default' => 'Východzie',
'sort_created_at' => 'Dátum vytvorenia',
'sort_updated_at' => 'Aktualizované dňa',
'no_activity' => 'Žiadna aktivita na zobrazenie',
'no_items' => 'Žiadne položky nie sú dostupné',
'back_to_top' => 'Späť nahor',
+ 'skip_to_main_content' => 'Preskočiť na hlavný obsah',
'toggle_details' => 'Prepnúť detaily',
'toggle_thumbnails' => 'Prepnúť náhľady',
'details' => 'Podrobnosti',
'breadcrumb' => 'Breadcrumb',
// Header
- 'header_menu_expand' => 'Expand Header Menu',
+ 'header_menu_expand' => 'Rozbaliť menu v záhlaví',
'profile_menu' => 'Menu profilu',
'view_profile' => 'Zobraziť profil',
'edit_profile' => 'Upraviť profil',
// Layout tabs
'tab_info' => 'Informácie',
- 'tab_info_label' => 'Tab: Show Secondary Information',
+ 'tab_info_label' => 'Tab: Zobraziť vedľajšie informácie',
'tab_content' => 'Obsah',
- 'tab_content_label' => 'Tab: Show Primary Content',
+ 'tab_content_label' => 'Tab: Zobraziť hlavné informácie',
// Email Content
'email_action_help' => 'Ak máte problém klinkúť na tlačidlo ":actionText", skopírujte a vložte URL uvedenú nižšie do Vášho prehliadača:',
// Footer Link Options
// Not directly used but available for convenience to users.
- 'privacy_policy' => 'Privacy Policy',
- 'terms_of_service' => 'Terms of Service',
+ 'privacy_policy' => 'Zásady ochrany osobných údajov',
+ 'terms_of_service' => 'Podmienky používania',
];
'meta_created_name' => 'Vytvorené :timeLength používateľom :user',
'meta_updated' => 'Aktualizované :timeLength',
'meta_updated_name' => 'Aktualizované :timeLength používateľom :user',
- 'meta_owned_name' => 'Owned by :user',
+ 'meta_owned_name' => 'Vlastník :user',
'entity_select' => 'Entita vybraná',
'images' => 'Obrázky',
'my_recent_drafts' => 'Moje nedávne koncepty',
'my_recently_viewed' => 'Nedávno mnou zobrazené',
- 'my_most_viewed_favourites' => 'My Most Viewed Favourites',
- 'my_favourites' => 'My Favourites',
+ 'my_most_viewed_favourites' => 'Moje najčastejšie zobrazené obľubené',
+ 'my_favourites' => 'Moje obľúbené',
'no_pages_viewed' => 'Nepozreli ste si žiadne stránky',
'no_pages_recently_created' => 'Žiadne stránky neboli nedávno vytvorené',
'no_pages_recently_updated' => 'Žiadne stránky neboli nedávno aktualizované',
'export_html' => 'Obsahovaný webový súbor',
'export_pdf' => 'PDF súbor',
'export_text' => 'Súbor s čistým textom',
+ 'export_md' => 'Súbor Markdown',
// Permissions and restrictions
'permissions' => 'Oprávnenia',
'permissions_intro' => 'Ak budú tieto oprávnenia povolené, budú mať prioritu pred oprávneniami roly.',
'permissions_enable' => 'Povoliť vlastné oprávnenia',
'permissions_save' => 'Uložiť oprávnenia',
- 'permissions_owner' => 'Owner',
+ 'permissions_owner' => 'Vlastník',
// Search
'search_results' => 'Výsledky hľadania',
'search_permissions_set' => 'Oprávnenia',
'search_created_by_me' => 'Vytvorené mnou',
'search_updated_by_me' => 'Aktualizované mnou',
- 'search_owned_by_me' => 'Owned by me',
+ 'search_owned_by_me' => 'Patriace mne',
'search_date_options' => 'Možnosti dátumu',
'search_updated_before' => 'Aktualizované pred',
'search_updated_after' => 'Aktualizované po',
'shelves_permissions' => 'Oprávnenia knižnice',
'shelves_permissions_updated' => 'Oprávnenia knižnice aktualizované',
'shelves_permissions_active' => 'Oprávnenia knižnice aktívne',
- 'shelves_copy_permissions_to_books' => 'Copy Permissions to Books',
- 'shelves_copy_permissions' => 'Copy Permissions',
+ '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' => 'Kopírovať oprávnenia pre knihy',
+ 'shelves_copy_permissions' => 'Kopírovať oprávnenia',
'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',
+ 'shelves_copy_permission_success' => 'Oprávnenia knižnice boli skopírované {0}:count kníh|{1}:count kniha|[2,3,4]:count knihy|[5,*]:count kníh',
// Books
'book' => 'Kniha',
'books' => 'Knihy',
- 'x_books' => ':count Book|:count Books',
+ 'x_books' => '{0}:count kníh|{1}:count kniha|[2,3,4]:count knihy|[5,*]:count kníh',
'books_empty' => 'Žiadne knihy neboli vytvorené',
'books_popular' => 'Populárne knihy',
'books_recent' => 'Nedávne knihy',
- 'books_new' => 'New Books',
- 'books_new_action' => 'New Book',
+ 'books_new' => 'Nové knihy',
+ 'books_new_action' => 'Nová kniha',
'books_popular_empty' => 'Najpopulárnejšie knihy sa objavia tu.',
- 'books_new_empty' => 'The most recently created books will appear here.',
+ 'books_new_empty' => 'Najnovšie knihy sa zobrazia tu.',
'books_create' => 'Vytvoriť novú knihu',
'books_delete' => 'Zmazať knihu',
'books_delete_named' => 'Zmazať knihu :bookName',
'books_navigation' => 'Navigácia knihy',
'books_sort' => 'Zoradiť obsah knihy',
'books_sort_named' => 'Zoradiť knihu :bookName',
- 'books_sort_name' => 'Sort by Name',
- 'books_sort_created' => 'Sort by Created Date',
- 'books_sort_updated' => 'Sort by Updated Date',
- 'books_sort_chapters_first' => 'Chapters First',
- 'books_sort_chapters_last' => 'Chapters Last',
+ 'books_sort_name' => 'Zoradiť podľa mena',
+ 'books_sort_created' => 'Zoradiť podľa dátumu vytvorenia',
+ 'books_sort_updated' => 'Zoradiť podľa dátumu aktualizácie',
+ 'books_sort_chapters_first' => 'Kapitoly ako prvé',
+ 'books_sort_chapters_last' => 'Kapitoly ako posledné',
'books_sort_show_other' => 'Zobraziť ostatné knihy',
'books_sort_save' => 'Uložiť nové zoradenie',
// Chapters
'chapter' => 'Kapitola',
'chapters' => 'Kapitoly',
- 'x_chapters' => ':count Chapter|:count Chapters',
+ 'x_chapters' => '{0}:count Kapitol|{1}:count Kapitola|[2,3,4]:count Kapitoly|[5,*]:count Kapitol',
'chapters_popular' => 'Populárne kapitoly',
'chapters_new' => 'Nová kapitola',
'chapters_create' => 'Vytvoriť novú kapitolu',
'chapters_delete' => 'Zmazať kapitolu',
'chapters_delete_named' => 'Zmazať kapitolu :chapterName',
- 'chapters_delete_explain' => 'This will delete the chapter with the name \':chapterName\'. All pages that exist within this chapter will also be deleted.',
+ 'chapters_delete_explain' => 'Týmto sa odstráni kapitola s názvom \':chapterName\'. Spolu s ňou sa odstránia všetky stránky v tejto kapitole.',
'chapters_delete_confirm' => 'Ste si istý, že chcete zmazať túto kapitolu?',
'chapters_edit' => 'Upraviť kapitolu',
'chapters_edit_named' => 'Upraviť kapitolu :chapterName',
'chapters_empty' => 'V tejto kapitole nie sú teraz žiadne stránky.',
'chapters_permissions_active' => 'Oprávnenia kapitoly aktívne',
'chapters_permissions_success' => 'Oprávnenia kapitoly aktualizované',
- 'chapters_search_this' => 'Search this chapter',
+ 'chapters_search_this' => 'Hladať v kapitole',
// Pages
'page' => 'Stránka',
'pages_delete_confirm' => 'Ste si istý, že chcete zmazať túto stránku?',
'pages_delete_draft_confirm' => 'Ste si istý, že chcete zmazať tento koncept stránky?',
'pages_editing_named' => 'Upraviť stránku :pageName',
- 'pages_edit_draft_options' => 'Draft Options',
+ 'pages_edit_draft_options' => 'Možnosti konceptu',
'pages_edit_save_draft' => 'Uložiť koncept',
'pages_edit_draft' => 'Upraviť koncept stránky',
'pages_editing_draft' => 'Upravuje sa koncept',
'pages_md_preview' => 'Náhľad',
'pages_md_insert_image' => 'Vložiť obrázok',
'pages_md_insert_link' => 'Vložiť odkaz na entitu',
- 'pages_md_insert_drawing' => 'Insert Drawing',
+ 'pages_md_insert_drawing' => 'Vložiť kresbu',
'pages_not_in_chapter' => 'Stránka nie je v kapitole',
'pages_move' => 'Presunúť stránku',
'pages_move_success' => 'Stránka presunutá do ":parentName"',
- 'pages_copy' => 'Copy Page',
- 'pages_copy_desination' => 'Copy Destination',
- 'pages_copy_success' => 'Page successfully copied',
+ 'pages_copy' => 'Kpoírovať stránku',
+ 'pages_copy_desination' => 'Ciel kopírovania',
+ 'pages_copy_success' => 'Stránka bola skopírovaná',
'pages_permissions' => 'Oprávnenia stránky',
'pages_permissions_success' => 'Oprávnenia stránky aktualizované',
- 'pages_revision' => 'Revision',
+ 'pages_revision' => 'Revízia',
'pages_revisions' => 'Revízie stránky',
'pages_revisions_named' => 'Revízie stránky :pageName',
'pages_revision_named' => 'Revízia stránky :pageName',
- 'pages_revision_restored_from' => 'Restored from #:id; :summary',
+ 'pages_revision_restored_from' => 'Obnovené z #:id; :summary',
'pages_revisions_created_by' => 'Vytvoril',
'pages_revisions_date' => 'Dátum revízie',
- 'pages_revisions_number' => '#',
- 'pages_revisions_numbered' => 'Revision #:id',
- 'pages_revisions_numbered_changes' => 'Revision #:id Changes',
+ 'pages_revisions_number' => 'č.',
+ 'pages_revisions_numbered' => 'Revízia č. :id',
+ 'pages_revisions_numbered_changes' => 'Zmeny revízie č. ',
'pages_revisions_changelog' => 'Záznam zmien',
'pages_revisions_changes' => 'Zmeny',
'pages_revisions_current' => 'Aktuálna verzia',
'message' => ':start :time. Dávajte pozor aby ste si navzájom neprepísali zmeny!',
],
'pages_draft_discarded' => 'Koncept ostránený, aktuálny obsah stránky bol nahraný do editora',
- 'pages_specific' => 'Specific Page',
- 'pages_is_template' => 'Page Template',
+ 'pages_specific' => 'Konkrétna stránka',
+ 'pages_is_template' => 'Šablóna stránky',
// Editor Sidebar
'page_tags' => 'Štítky stránok',
- 'chapter_tags' => 'Chapter Tags',
- 'book_tags' => 'Book Tags',
- 'shelf_tags' => 'Shelf Tags',
+ 'chapter_tags' => 'Štítky kapitol',
+ 'book_tags' => 'Štítky kníh',
+ 'shelf_tags' => 'Štítky knižníc',
'tag' => 'Štítok',
'tags' => 'Štítky',
- 'tag_name' => 'Tag Name',
+ 'tag_name' => 'Názov štítku',
'tag_value' => 'Hodnota štítku (Voliteľné)',
'tags_explain' => "Pridajte pár štítkov pre uľahčenie kategorizácie Vášho obsahu. \n Štítku môžete priradiť hodnotu pre ešte lepšiu organizáciu.",
'tags_add' => 'Pridať ďalší štítok',
- 'tags_remove' => 'Remove this tag',
+ 'tags_remove' => 'Odstrániť tento štítok',
'attachments' => 'Prílohy',
'attachments_explain' => 'Nahrajte nejaké súbory alebo priložte zopár odkazov pre zobrazenie na Vašej stránke. Budú viditeľné v bočnom paneli.',
'attachments_explain_instant_save' => 'Zmeny budú okamžite uložené.',
'attachments_file_uploaded' => 'Súbor úspešne nahraný',
'attachments_file_updated' => 'Súbor úspešne aktualizovaný',
'attachments_link_attached' => 'Odkaz úspešne pripojený k stránke',
- 'templates' => 'Templates',
- 'templates_set_as_template' => 'Page is a template',
+ 'templates' => 'Šablóny',
+ 'templates_set_as_template' => 'Táto stránka je šablóna',
'templates_explain_set_as_template' => 'You can set this page as a template so its contents be utilized when creating other pages. Other users will be able to use this template if they have view permissions for this page.',
'templates_replace_content' => 'Replace page content',
'templates_append_content' => 'Append to page content',
// Comments
'comment' => 'Komentár',
'comments' => 'Komentáre',
- 'comment_add' => 'Add Comment',
+ 'comment_add' => 'Pridať komentár',
'comment_placeholder' => 'Tu zadajte svoje pripomienky',
- 'comment_count' => '{0} No Comments|{1} 1 Comment|[2,*] :count Comments',
+ 'comment_count' => '{0} Bez komentárov|{1} 1 komentár|[2,3,4] :count komentáre|[5,*] :count komentárov',
'comment_save' => 'Uložiť komentár',
- 'comment_saving' => 'Saving comment...',
- 'comment_deleting' => 'Deleting comment...',
- 'comment_new' => 'New Comment',
- 'comment_created' => 'commented :createDiff',
+ 'comment_saving' => 'Ukladanie komentára...',
+ 'comment_deleting' => 'Mazanie komentára...',
+ 'comment_new' => 'Nový komentár',
+ 'comment_created' => 'komentované :createDiff',
'comment_updated' => 'Updated :updateDiff by :username',
- 'comment_deleted_success' => 'Comment deleted',
- 'comment_created_success' => 'Comment added',
- 'comment_updated_success' => 'Comment updated',
+ 'comment_deleted_success' => 'Komentár odstránený',
+ 'comment_created_success' => 'Komentár pridaný',
+ 'comment_updated_success' => 'Komentár aktualizovaný',
'comment_delete_confirm' => 'Ste si istý, že chcete odstrániť tento komentár?',
'comment_in_reply_to' => 'Odpovedať na :commentId',
'email_already_confirmed' => 'Email bol už overený, skúste sa prihlásiť.',
'email_confirmation_invalid' => 'Tento potvrdzujúci token nie je platný alebo už bol použitý, skúste sa prosím registrovať znova.',
'email_confirmation_expired' => 'Potvrdzujúci token expiroval, bol odoslaný nový potvrdzujúci email.',
- 'email_confirmation_awaiting' => 'The email address for the account in use needs to be confirmed',
+ 'email_confirmation_awaiting' => 'Potvrďte emailovú adresu pre užívateľský účet',
'ldap_fail_anonymous' => 'LDAP access failed using anonymous bind',
'ldap_fail_authed' => 'LDAP access failed using given dn & password details',
'ldap_extension_not_installed' => 'LDAP PHP extension not installed',
'404_page_not_found' => 'Stránka nenájdená',
'sorry_page_not_found' => 'Prepáčte, stránka ktorú hľadáte nebola nájdená.',
'sorry_page_not_found_permission_warning' => 'If you expected this page to exist, you might not have permission to view it.',
- 'image_not_found' => 'Image Not Found',
+ 'image_not_found' => 'Obrázok nebol nájdený',
'image_not_found_subtitle' => 'Sorry, The image file you were looking for could not be found.',
'image_not_found_details' => 'If you expected this image to exist it might have been deleted.',
'return_home' => 'Vrátiť sa domov',
'settings_save_success' => 'Nastavenia uložené',
// App Settings
- 'app_customization' => 'Customization',
- 'app_features_security' => 'Features & Security',
+ 'app_customization' => 'Prispôsobenia',
+ 'app_features_security' => 'Funkcie a bezpečnosť',
'app_name' => 'Názov aplikácia',
'app_name_desc' => 'Tento názov sa zobrazuje v hlavičke a v emailoch.',
'app_name_header' => 'Zobraziť názov aplikácie v hlavičke?',
- 'app_public_access' => 'Public Access',
+ 'app_public_access' => 'Verejný prístup',
'app_public_access_desc' => 'Enabling this option will allow visitors, that are not logged-in, to access content in your BookStack instance.',
'app_public_access_desc_guest' => 'Access for public visitors can be controlled through the "Guest" user.',
- 'app_public_access_toggle' => 'Allow public access',
+ 'app_public_access_toggle' => 'Povoliť verejný prístup',
'app_public_viewing' => 'Povoliť verejné zobrazenie?',
'app_secure_images' => 'Povoliť nahrávanie súborov so zvýšeným zabezpečením?',
'app_secure_images_toggle' => 'Enable higher security image uploads',
'app_logo_desc' => 'Tento obrázok by mal mať 43px na výšku. <br>Veľké obrázky budú preškálované na menší rozmer.',
'app_primary_color' => 'Primárna farba pre aplikáciu',
'app_primary_color_desc' => 'Toto by mala byť hodnota v hex tvare. <br>Nechajte prázdne ak chcete použiť prednastavenú farbu.',
- 'app_homepage' => 'Application Homepage',
+ 'app_homepage' => 'Domovská stránka aplikácie',
'app_homepage_desc' => 'Select a view to show on the homepage instead of the default view. Page permissions are ignored for selected pages.',
- 'app_homepage_select' => 'Select a page',
- 'app_footer_links' => 'Footer Links',
+ 'app_homepage_select' => 'Vybrať stránku',
+ 'app_footer_links' => 'Odkazy v pätičke',
'app_footer_links_desc' => 'Add links to show within the site footer. These will be displayed at the bottom of most pages, including those that do not require login. You can use a label of "trans::<key>" to use system-defined translations. For example: Using "trans::common.privacy_policy" will provide the translated text "Privacy Policy" and "trans::common.terms_of_service" will provide the translated text "Terms of Service".',
'app_footer_links_label' => 'Link Label',
'app_footer_links_url' => 'Link URL',
'app_footer_links_add' => 'Add Footer Link',
'app_disable_comments' => 'Zakázať komentáre',
- 'app_disable_comments_toggle' => 'Disable comments',
+ 'app_disable_comments_toggle' => 'Vypnúť komentáre',
'app_disable_comments_desc' => 'Zakázať komentáre na všetkých stránkach aplikácie. Existujúce komentáre sa nezobrazujú.',
// Color settings
- 'content_colors' => 'Content Colors',
+ 'content_colors' => 'Farby obsahu',
'content_colors_desc' => 'Sets colors for all elements in the page organisation hierarchy. Choosing colors with a similar brightness to the default colors is recommended for readability.',
'bookshelf_color' => 'Shelf Color',
'book_color' => 'Book Color',
// Registration Settings
'reg_settings' => 'Nastavenia registrácie',
- 'reg_enable' => 'Enable Registration',
- 'reg_enable_toggle' => 'Enable registration',
+ 'reg_enable' => 'Povolenie registrácie',
+ 'reg_enable_toggle' => 'Povoliť registrácie',
'reg_enable_desc' => 'When registration is enabled user will be able to sign themselves up as an application user. Upon registration they are given a single, default user role.',
'reg_default_role' => 'Prednastavená používateľská rola po registrácii',
'reg_enable_external_warning' => 'The option above is ignored while external LDAP or SAML authentication is active. User accounts for non-existing members will be auto-created if authentication, against the external system in use, is successful.',
- 'reg_email_confirmation' => 'Email Confirmation',
+ 'reg_email_confirmation' => 'Potvrdenie e-mailom',
'reg_email_confirmation_toggle' => 'Require email confirmation',
'reg_confirm_email_desc' => 'Ak je použité obmedzenie domény, potom bude vyžadované overenie emailu a hodnota nižšie bude ignorovaná.',
'reg_confirm_restrict_domain' => 'Obmedziť registráciu na doménu',
'reg_confirm_restrict_domain_placeholder' => 'Nie sú nastavené žiadne obmedzenia',
// Maintenance settings
- 'maint' => 'Maintenance',
- 'maint_image_cleanup' => 'Cleanup Images',
+ 'maint' => 'Údržba',
+ 'maint_image_cleanup' => 'Prečistenie obrázkov',
'maint_image_cleanup_desc' => "Scans page & revision content to check which images and drawings are currently in use and which images are redundant. Ensure you create a full database and image backup before running this.",
'maint_delete_images_only_in_revisions' => 'Also delete images that only exist in old page revisions',
- 'maint_image_cleanup_run' => 'Run Cleanup',
+ 'maint_image_cleanup_run' => 'Spustiť prečistenie',
'maint_image_cleanup_warning' => ':count potentially unused images were found. Are you sure you want to delete these images?',
'maint_image_cleanup_success' => ':count potentially unused images found and deleted!',
- 'maint_image_cleanup_nothing_found' => 'No unused images found, Nothing deleted!',
- 'maint_send_test_email' => 'Send a Test Email',
+ 'maint_image_cleanup_nothing_found' => 'Žiadne nepoužit obrázky neboli nájdené. Nič sa nezmazalo!',
+ 'maint_send_test_email' => 'Odoslať testovací email',
'maint_send_test_email_desc' => 'This sends a test email to your email address specified in your profile.',
- 'maint_send_test_email_run' => 'Send test email',
+ 'maint_send_test_email_run' => 'Odoslať testovací email',
'maint_send_test_email_success' => 'Email sent to :address',
- 'maint_send_test_email_mail_subject' => 'Test Email',
+ 'maint_send_test_email_mail_subject' => 'Testovací email',
'maint_send_test_email_mail_greeting' => 'Email delivery seems to work!',
'maint_send_test_email_mail_text' => 'Congratulations! As you received this email notification, your email settings seem to be configured properly.',
'maint_recycle_bin_desc' => 'Deleted shelves, books, chapters & pages are sent to the recycle bin so they can be restored or permanently deleted. Older items in the recycle bin may be automatically removed after a while depending on system configuration.',
- 'maint_recycle_bin_open' => 'Open Recycle Bin',
+ 'maint_recycle_bin_open' => 'Otvoriť kôš',
// Recycle Bin
- 'recycle_bin' => 'Recycle Bin',
+ 'recycle_bin' => 'Kôš',
'recycle_bin_desc' => 'Here you can restore items that have been deleted or choose to permanently remove them from the system. This list is unfiltered unlike similar activity lists in the system where permission filters are applied.',
- 'recycle_bin_deleted_item' => 'Deleted Item',
+ 'recycle_bin_deleted_item' => 'Odstránené položky',
+ 'recycle_bin_deleted_parent' => 'Parent',
'recycle_bin_deleted_by' => 'Deleted By',
'recycle_bin_deleted_at' => 'Deletion Time',
'recycle_bin_permanently_delete' => 'Permanently Delete',
'recycle_bin_restore_list' => 'Items to be Restored',
'recycle_bin_restore_confirm' => 'This action will restore the deleted item, including any child elements, to their original location. If the original location has since been deleted, and is now in the recycle bin, the parent item will also need to be restored.',
'recycle_bin_restore_deleted_parent' => 'The parent of this item has also been deleted. These will remain deleted until that parent is also restored.',
+ 'recycle_bin_restore_parent' => 'Restore Parent',
'recycle_bin_destroy_notification' => 'Deleted :count total items from the recycle bin.',
'recycle_bin_restore_notification' => 'Restored :count total items from the recycle bin.',
'audit_event_filter_no_filter' => 'No Filter',
'audit_deleted_item' => 'Deleted Item',
'audit_deleted_item_name' => 'Name: :name',
- 'audit_table_user' => 'User',
- 'audit_table_event' => 'Event',
+ 'audit_table_user' => 'Užívateľ',
+ 'audit_table_event' => 'Udalosť',
'audit_table_related' => 'Related Item or Detail',
- 'audit_table_date' => 'Activity Date',
+ 'audit_table_ip' => 'IP adresa',
+ 'audit_table_date' => 'Dátum aktivity',
'audit_date_from' => 'Date Range From',
'audit_date_to' => 'Date Range To',
'role_details' => 'Detaily roly',
'role_name' => 'Názov roly',
'role_desc' => 'Krátky popis roly',
+ 'role_mfa_enforced' => 'Requires Multi-Factor Authentication',
'role_external_auth_id' => 'External Authentication IDs',
'role_system' => 'Systémové oprávnenia',
'role_manage_users' => 'Spravovať používateľov',
'role_manage_page_templates' => 'Manage page templates',
'role_access_api' => 'Access system API',
'role_manage_settings' => 'Spravovať nastavenia aplikácie',
+ 'role_export_content' => 'Export content',
'role_asset' => 'Oprávnenia majetku',
'roles_system_warning' => 'Be aware that access to any of the above three permissions can allow a user to alter their own privileges or the privileges of others in the system. Only assign roles with these permissions to trusted users.',
'role_asset_desc' => 'Tieto oprávnenia regulujú prednastavený prístup k zdroju v systéme. Oprávnenia pre knihy, kapitoly a stránky majú vyššiu prioritu.',
'user_profile' => 'Profil používateľa',
'users_add_new' => 'Pridať nového používateľa',
'users_search' => 'Hľadať medzi používateľmi',
- 'users_latest_activity' => 'Latest Activity',
- 'users_details' => 'User Details',
+ 'users_latest_activity' => 'Nedávna aktivita',
+ 'users_details' => 'Údaje o používateľovi',
'users_details_desc' => 'Set a display name and an email address for this user. The email address will be used for logging into the application.',
'users_details_desc_no_email' => 'Set a display name for this user so others can recognise them.',
'users_role' => 'Používateľské roly',
'users_role_desc' => 'Select which roles this user will be assigned to. If a user is assigned to multiple roles the permissions from those roles will stack and they will receive all abilities of the assigned roles.',
- 'users_password' => 'User Password',
+ 'users_password' => 'Heslo používateľa',
'users_password_desc' => 'Set a password used to log-in to the application. This must be at least 6 characters long.',
'users_send_invite_text' => 'You can choose to send this user an invitation email which allows them to set their own password otherwise you can set their password yourself.',
'users_send_invite_option' => 'Send user invite email',
'users_api_tokens_create' => 'Create Token',
'users_api_tokens_expires' => 'Expires',
'users_api_tokens_docs' => 'API Documentation',
+ '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',
// API Tokens
'user_api_token_create' => 'Create API Token',
'it' => 'Italian',
'ja' => '日本語',
'ko' => '한국어',
+ 'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nl' => 'Nederlands',
'nb' => 'Norsk (Bokmål)',
'alpha_dash' => ':attribute môže obsahovať iba písmená, čísla a pomlčky.',
'alpha_num' => ':attribute môže obsahovať iba písmená a čísla.',
'array' => ':attribute musí byť pole.',
+ 'backup_codes' => 'The provided code is not valid or has already been used.',
'before' => ':attribute musí byť dátum pred :date.',
'between' => [
'numeric' => ':attribute musí byť medzi :min a :max.',
],
'string' => ':attribute musí byť reťazec.',
'timezone' => ':attribute musí byť plantá časová zóna.',
+ 'totp' => 'The provided code is not valid or has expired.',
'unique' => ':attribute je už použité.',
'url' => ':attribute formát je neplatný.',
'uploaded' => 'The file could not be uploaded. The server may not accept files of this size.',
'favourite_add_notification' => '":name" has been added to your favourites',
'favourite_remove_notification' => '":name" has been removed from your favourites',
+ // MFA
+ 'mfa_setup_method_notification' => 'Multi-factor method successfully configured',
+ 'mfa_remove_method_notification' => 'Multi-factor method successfully removed',
+
// Other
'commented_on' => 'komentar na',
'permissions_update' => 'pravice so posodobljene',
'user_invite_page_welcome' => 'Dobrodošli na :appName!',
'user_invite_page_text' => 'Za zaključiti in pridobiti dostop si morate nastaviti geslo, ki bo uporabljeno za prijavo v :appName.',
'user_invite_page_confirm_button' => 'Potrdi geslo',
- 'user_invite_success' => 'Geslo nastavljeno, sedaj imaš dostop do :appName!'
+ 'user_invite_success' => 'Geslo nastavljeno, sedaj imaš dostop do :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_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.',
];
\ No newline at end of file
'reset' => 'Ponastavi',
'remove' => 'Odstrani',
'add' => 'Dodaj',
+ 'configure' => 'Configure',
'fullscreen' => 'Celozaslonski način',
'favourite' => 'Favourite',
'unfavourite' => 'Unfavourite',
'no_activity' => 'Ni aktivnosti za prikaz',
'no_items' => 'Na voljo ni nobenega elementa',
'back_to_top' => 'Nazaj na vrh',
+ 'skip_to_main_content' => 'Skip to main content',
'toggle_details' => 'Preklopi podrobnosti',
'toggle_thumbnails' => 'Preklopi sličice',
'details' => 'Podrobnosti',
'export_html' => 'Vsebuje spletno datoteko',
'export_pdf' => 'PDF datoteka (.pdf)',
'export_text' => 'Navadna besedilna datoteka',
+ 'export_md' => 'Markdown File',
// Permissions and restrictions
'permissions' => 'Dovoljenja',
'shelves_permissions' => 'Dovoljenja knjižnih polic',
'shelves_permissions_updated' => 'Posodobljena dovoljenja knjižnih polic',
'shelves_permissions_active' => 'Aktivna dovoljenja knjižnih polic',
+ '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' => 'Kopiraj dovoljenja na knjige',
'shelves_copy_permissions' => 'Dovoljenja kopiranja',
'shelves_copy_permissions_explain' => 'To bo uveljavilo trenutne nastavitve dovoljenj na knjižni polici za vse knjige, ki jih vsebuje ta polica. Pred aktiviranjem zagotovite, da so shranjene vse spremembe dovoljenj te knjižne police.',
'recycle_bin' => 'Koš',
'recycle_bin_desc' => 'Tu lahko obnovite predmete, ki so bili izbrisani, ali pa jih trajno odstranite s sistema. Ta seznam je nefiltriran, za razliko od podobnih seznamov dejavnosti v sistemu, kjer se uporabljajo filtri dovoljenj.',
'recycle_bin_deleted_item' => 'Izbrisan element',
+ 'recycle_bin_deleted_parent' => 'Parent',
'recycle_bin_deleted_by' => 'Izbrisal uporabnik',
'recycle_bin_deleted_at' => 'Čas izbrisa',
'recycle_bin_permanently_delete' => 'Trajno izbrišem?',
'recycle_bin_restore_list' => 'Predmeti, ki naj bodo obnovljeni',
'recycle_bin_restore_confirm' => 'S tem dejanjem boste izbrisani element, vključno z vsemi podrejenimi elementi, obnovili na prvotno mesto. Če je bilo prvotno mesto od takrat izbrisano in je zdaj v košu, bo treba obnoviti tudi nadrejeni element.',
'recycle_bin_restore_deleted_parent' => 'Nadrejeni element je bil prav tako izbrisan. Dokler se ne obnovi nadrejenega elementa, ni mogoče obnoviti njemu podrejenih elementov.',
+ 'recycle_bin_restore_parent' => 'Restore Parent',
'recycle_bin_destroy_notification' => 'Izbrisano :count skupno število elementov iz koša.',
'recycle_bin_restore_notification' => 'Obnovljeno :count skupno število elementov iz koša.',
'audit_table_user' => 'Uporabnik',
'audit_table_event' => 'Dogodek',
'audit_table_related' => 'Povezani predmet ali podrobnost',
+ 'audit_table_ip' => 'IP Address',
'audit_table_date' => 'Datum zadnje dejavnosti',
'audit_date_from' => 'Časovno obdobje od',
'audit_date_to' => 'Časovno obdobje do',
'role_details' => 'Podrobnosti vloge',
'role_name' => 'Naziv vloge',
'role_desc' => 'Kratki opis vloge',
+ 'role_mfa_enforced' => 'Requires Multi-Factor Authentication',
'role_external_auth_id' => 'Zunanje dokazilo ID',
'role_system' => 'Sistemska dovoljenja',
'role_manage_users' => 'Upravljanje uporabnikov',
'role_manage_page_templates' => 'Uredi predloge',
'role_access_api' => 'API za dostop do sistema',
'role_manage_settings' => 'Nastavitve za upravljanje',
+ 'role_export_content' => 'Export content',
'role_asset' => 'Sistemska dovoljenja',
'roles_system_warning' => 'Zavedajte se, da lahko dostop do kateregakoli od zgornjih treh dovoljenj uporabniku omogoči, da spremeni lastne privilegije ali privilegije drugih v sistemu. Vloge s temi dovoljenji dodelite samo zaupanja vrednim uporabnikom.',
'role_asset_desc' => 'Ta dovoljenja nadzorujejo privzeti dostop do sredstev v sistemu. Dovoljenja za knjige, poglavja in strani bodo razveljavila ta dovoljenja.',
'users_api_tokens_create' => 'Ustvari žeton',
'users_api_tokens_expires' => 'Poteče',
'users_api_tokens_docs' => 'API dokumentacija',
+ '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',
// API Tokens
'user_api_token_create' => 'Ustvari žeton',
'it' => 'Italian',
'ja' => '日本語',
'ko' => '한국어',
+ 'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nl' => 'Nederlands',
'nb' => 'Norsk (Bokmål)',
'alpha_dash' => ':attribute lahko vsebuje samo ?rke, ?tevilke in ?rtice.',
'alpha_num' => ':attribute lahko vsebuje samo črke in številke.',
'array' => ':attribute mora biti niz.',
+ 'backup_codes' => 'The provided code is not valid or has already been used.',
'before' => ':attribute mora biti datum pred :date.',
'between' => [
'numeric' => ':attribute mora biti med :min in :max.',
],
'string' => ':attribute mora biti niz.',
'timezone' => ':attribute mora biti veljavna cona.',
+ 'totp' => 'The provided code is not valid or has expired.',
'unique' => ':attribute je že zaseden.',
'url' => ':attribute oblika ni veljavna.',
'uploaded' => 'Datoteke ni bilo mogoče naložiti. Strežnik morda ne sprejema datotek te velikosti.',
'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',
+
// Other
'commented_on' => 'kommenterade',
'permissions_update' => 'uppdaterade behörigheter',
'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' => 'Lösenord satt, du har nu tillgång till :appName!'
+ 'user_invite_success' => 'Lösenord satt, du har nu tillgång till :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_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.',
];
\ No newline at end of file
'reset' => 'Återställ',
'remove' => 'Radera',
'add' => 'Lägg till',
+ 'configure' => 'Configure',
'fullscreen' => 'Helskärm',
'favourite' => 'Favorit',
'unfavourite' => 'Ta bort favorit',
'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',
'toggle_details' => 'Växla detaljer',
'toggle_thumbnails' => 'Växla miniatyrer',
'details' => 'Information',
'export_html' => 'Webb-fil',
'export_pdf' => 'PDF-fil',
'export_text' => 'Textfil',
+ 'export_md' => 'Markdown File',
// Permissions and restrictions
'permissions' => 'Rättigheter',
'shelves_permissions' => 'Bokhyllerättigheter',
'shelves_permissions_updated' => 'Bokhyllerättigheterna har ändrats',
'shelves_permissions_active' => 'Bokhyllerättigheterna är aktiva',
+ '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' => 'Kopiera rättigheter till böcker',
'shelves_copy_permissions' => 'Kopiera rättigheter',
'shelves_copy_permissions_explain' => 'Detta kommer kopiera hyllans rättigheter till alla böcker på den. Se till att du har sparat alla ändringar innan du går vidare.',
'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_by' => 'Borttagen av',
'recycle_bin_deleted_at' => 'Tid för borttagning',
'recycle_bin_permanently_delete' => 'Radera permanent',
'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_destroy_notification' => 'Raderade :count totala objekt från papperskorgen.',
'recycle_bin_restore_notification' => 'Återställt :count totala objekt från papperskorgen.',
'audit_table_user' => 'Användare',
'audit_table_event' => 'Händelse',
'audit_table_related' => 'Relaterat objekt eller detalj',
+ 'audit_table_ip' => 'IP Address',
'audit_table_date' => 'Datum för senaste aktiviteten',
'audit_date_from' => 'Datumintervall från',
'audit_date_to' => 'Datumintervall till',
'role_details' => 'Om rollen',
'role_name' => 'Rollens namn',
'role_desc' => 'Kort beskrivning av rollen',
+ 'role_mfa_enforced' => 'Requires Multi-Factor Authentication',
'role_external_auth_id' => 'Externa autentiserings-ID:n',
'role_system' => 'Systemrättigheter',
'role_manage_users' => 'Hanter användare',
'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_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.',
'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',
// API Tokens
'user_api_token_create' => 'Skapa API-nyckel',
'it' => 'Italian',
'ja' => '日本語',
'ko' => '한국어',
+ 'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nl' => 'Nederlands',
'nb' => 'Norsk (Bokmål)',
'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.',
'before' => ':attribute måste vara före :date.',
'between' => [
'numeric' => ':attribute måste vara mellan :min och :max.',
],
'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.',
'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.',
'favourite_add_notification' => '":name" favorilerinize eklendi',
'favourite_remove_notification' => '":name" favorilerinizden çıkarıldı',
+ // MFA
+ 'mfa_setup_method_notification' => 'Multi-factor method successfully configured',
+ 'mfa_remove_method_notification' => 'Multi-factor method successfully removed',
+
// Other
'commented_on' => 'yorum yaptı',
'permissions_update' => 'güncellenmiş izinler',
'user_invite_page_welcome' => ':appName uygulamasına hoş geldiniz!',
'user_invite_page_text' => 'Hesap kurulumunuzu tamamlamak ve gelecekteki :appName ziyaretlerinizde hesabınıza erişim sağlayabilmeniz için bir şifre belirlemeniz gerekiyor.',
'user_invite_page_confirm_button' => 'Şifreyi Onayla',
- 'user_invite_success' => 'Şifreniz ayarlandı, artık :appName uygulamasına giriş yapabilirsiniz!'
+ 'user_invite_success' => 'Şifreniz ayarlandı, artık :appName uygulamasına giriş yapabilirsiniz!',
+
+ // 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_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.',
];
\ No newline at end of file
'reset' => 'Sıfırla',
'remove' => 'Kaldır',
'add' => 'Ekle',
+ 'configure' => 'Configure',
'fullscreen' => 'Tam Ekran',
'favourite' => 'Favourite',
'unfavourite' => 'Unfavourite',
'no_activity' => 'Gösterilecek eylem bulunamadı',
'no_items' => 'Herhangi bir öge bulunamadı',
'back_to_top' => 'Başa dön',
+ 'skip_to_main_content' => 'Skip to main content',
'toggle_details' => 'Detayları Göster/Gizle',
'toggle_thumbnails' => 'Ön İzleme Görsellerini Göster/Gizle',
'details' => 'Detaylar',
'export_html' => 'Web Dosyası',
'export_pdf' => 'PDF Dosyası',
'export_text' => 'Düz Metin Dosyası',
+ 'export_md' => 'Markdown File',
// Permissions and restrictions
'permissions' => 'İzinler',
'shelves_permissions' => 'Kitaplık İzinleri',
'shelves_permissions_updated' => 'Kitaplık İzinleri Güncellendi',
'shelves_permissions_active' => 'Kitaplık İzinleri Aktif',
+ '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' => 'İzinleri Kitaplara Kopyala',
'shelves_copy_permissions' => 'İzinleri Kopyala',
'shelves_copy_permissions_explain' => 'Bu işlem sonucunda kitaplığınızın izinleri, içerdiği kitaplara da aynen uygulanır. Aktifleştirmeden önce bu kitaplığa ait izinleri kaydettiğinizden emin olun.',
'recycle_bin' => 'Geri Dönüşüm Kutusu',
'recycle_bin_desc' => 'Burada silinen öğeleri geri yükleyebilir veya bunları sistemden kalıcı olarak kaldırmayı seçebilirsiniz. Bu liste, izin filtrelerinin uygulandığı sistemdeki benzer etkinlik listelerinden farklı olarak filtrelenmez.',
'recycle_bin_deleted_item' => 'Silinen öge',
+ 'recycle_bin_deleted_parent' => 'Parent',
'recycle_bin_deleted_by' => 'Tarafından silindi',
'recycle_bin_deleted_at' => 'Silinme Zamanı',
'recycle_bin_permanently_delete' => 'Kalıcı Olarak Sil',
'recycle_bin_restore_list' => 'Geri Yüklenecek Öğeler',
'recycle_bin_restore_confirm' => 'Bu eylem, tüm alt öğeler dahil olmak üzere silinen öğeyi orijinal konumlarına geri yükleyecektir. Orijinal konum o zamandan beri silinmişse ve şimdi geri dönüşüm kutusunda bulunuyorsa, üst öğenin de geri yüklenmesi gerekecektir.',
'recycle_bin_restore_deleted_parent' => 'Bu öğenin üst öğesi de silindi. Bunlar, üst öğe de geri yüklenene kadar silinmiş olarak kalacaktır.',
+ 'recycle_bin_restore_parent' => 'Restore Parent',
'recycle_bin_destroy_notification' => 'Deleted :count total items from the recycle bin.',
'recycle_bin_restore_notification' => 'Restored :count total items from the recycle bin.',
'audit_table_user' => 'Kullanıcı',
'audit_table_event' => 'Etkinlik',
'audit_table_related' => 'İlgili Öğe veya Detay',
+ 'audit_table_ip' => 'IP Address',
'audit_table_date' => 'Aktivite Tarihi',
'audit_date_from' => 'Tarih Aralığından',
'audit_date_to' => 'Tarih Aralığına',
'role_details' => 'Rol Detayları',
'role_name' => 'Rol Adı',
'role_desc' => 'Rolün Kısa Tanımı',
+ 'role_mfa_enforced' => 'Requires Multi-Factor Authentication',
'role_external_auth_id' => 'Harici Doğrulama Kimlikleri',
'role_system' => 'Sistem Yetkileri',
'role_manage_users' => 'Kullanıcıları yönet',
'role_manage_page_templates' => 'Sayfa şablonlarını yönet',
'role_access_api' => 'Sistem programlama arayüzüne (API) eriş',
'role_manage_settings' => 'Uygulama ayarlarını yönet',
+ 'role_export_content' => 'Export content',
'role_asset' => 'Varlık Yetkileri',
'roles_system_warning' => 'Yukarıdaki üç izinden herhangi birine erişimin, kullanıcının kendi ayrıcalıklarını veya sistemdeki diğerlerinin ayrıcalıklarını değiştirmesine izin verebileceğini unutmayın. Yalnızca bu izinlere sahip rolleri güvenilir kullanıcılara atayın.',
'role_asset_desc' => 'Bu izinler, sistem içindeki varlıklara varsayılan erişim izinlerini ayarlar. Kitaplar, bölümler ve sayfalar üzerindeki izinler, buradaki izinleri geçersiz kılar.',
'users_api_tokens_create' => 'Anahtar Oluştur',
'users_api_tokens_expires' => 'Bitiş süresi',
'users_api_tokens_docs' => 'API Dokümantasyonu',
+ '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',
// API Tokens
'user_api_token_create' => 'API Anahtarı Oluştur',
'it' => 'Italian',
'ja' => '日本語',
'ko' => '한국어',
+ 'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nl' => 'Nederlands',
'nb' => 'Norsk (Bokmål)',
'alpha_dash' => ':attribute sadece harf, rakam ve tirelerden oluşabilir.',
'alpha_num' => ':attribute sadece harflerden ve rakamlardan oluşabilir.',
'array' => ':attribute bir dizi olmalıdır.',
+ 'backup_codes' => 'The provided code is not valid or has already been used.',
'before' => ':attribute tarihi, :date tarihinden önceki bir tarih olmalıdır.',
'between' => [
'numeric' => ':attribute değeri, :min ve :max değerleri arasında olmalıdır.',
],
'string' => ':attribute, string olmalıdır.',
'timezone' => ':attribute, geçerli bir bölge olmalıdır.',
+ 'totp' => 'The provided code is not valid or has expired.',
'unique' => ':attribute zaten alınmış.',
'url' => ':attribute formatı geçersiz.',
'uploaded' => 'Dosya yüklemesi başarısız oldu. Sunucu, bu boyuttaki dosyaları kabul etmiyor olabilir.',
'favourite_add_notification' => '":name" has been added to your favourites',
'favourite_remove_notification' => '":name" has been removed from your favourites',
+ // MFA
+ 'mfa_setup_method_notification' => 'Multi-factor method successfully configured',
+ 'mfa_remove_method_notification' => 'Multi-factor method successfully removed',
+
// Other
'commented_on' => 'прокоментував',
'permissions_update' => 'оновив дозволи',
'user_invite_page_welcome' => 'Ласкаво просимо до :appName!',
'user_invite_page_text' => 'Для завершення процесу створення облікового запису та отримання доступу вам потрібно задати пароль, який буде використовуватися для входу в :appName в майбутньому.',
'user_invite_page_confirm_button' => 'Підтвердити пароль',
- 'user_invite_success' => 'Встановлено пароль, тепер у вас є доступ до :appName!'
+ 'user_invite_success' => 'Встановлено пароль, тепер у вас є доступ до :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_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.',
];
\ No newline at end of file
'reset' => 'Скинути',
'remove' => 'Видалити',
'add' => 'Додати',
+ 'configure' => 'Configure',
'fullscreen' => 'На весь екран',
'favourite' => 'Favourite',
'unfavourite' => 'Unfavourite',
'no_activity' => 'Немає активності для показу',
'no_items' => 'Немає доступних елементів',
'back_to_top' => 'Повернутися до початку',
+ 'skip_to_main_content' => 'Skip to main content',
'toggle_details' => 'Подробиці',
'toggle_thumbnails' => 'Мініатюри',
'details' => 'Деталі',
'export_html' => 'Вбудований веб-файл',
'export_pdf' => 'PDF файл',
'export_text' => 'Текстовий файл',
+ 'export_md' => 'Markdown File',
// Permissions and restrictions
'permissions' => 'Дозволи',
'shelves_permissions' => 'Дозволи на книжкову полицю',
'shelves_permissions_updated' => 'Дозволи на книжкову полицю оновлено',
'shelves_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' => 'Це застосовує поточні налаштування дозволів цієї книжкової полиці до всіх книг, що містяться всередині. Перш ніж активувати, переконайтесь що будь-які зміни дозволів цієї книжкової полиці були збережені.',
'recycle_bin' => 'Кошик',
'recycle_bin_desc' => 'Тут ви можете відновити видалені елементи, або назавжди видалити їх із системи. Цей список нефільтрований, на відміну від подібних списків активності в системі, де застосовуються фільтри дозволів.',
'recycle_bin_deleted_item' => 'Виадлений елемент',
+ 'recycle_bin_deleted_parent' => 'Parent',
'recycle_bin_deleted_by' => 'Ким видалено',
'recycle_bin_deleted_at' => 'Час видалення',
'recycle_bin_permanently_delete' => 'Видалити остаточно',
'recycle_bin_restore_list' => 'Елементи для відновлення',
'recycle_bin_restore_confirm' => 'Ця дія відновить видалений елемент у початкове місце, включаючи всі дочірні елементи. Якщо вихідне розташування відтоді було видалено, і знаходиться у кошику, батьківський елемент також потрібно буде відновити.',
'recycle_bin_restore_deleted_parent' => 'Батьківський елемент цього об\'єкта також був видалений. Вони залишатимуться видаленими, доки батьківський елемент також не буде відновлений.',
+ 'recycle_bin_restore_parent' => 'Restore Parent',
'recycle_bin_destroy_notification' => 'Видалено :count елементів із кошика.',
'recycle_bin_restore_notification' => 'Відновлено :count елементів із кошика.',
'audit_table_user' => 'Користувач',
'audit_table_event' => 'Подія',
'audit_table_related' => 'Пов’язаний елемент',
+ 'audit_table_ip' => 'IP Address',
'audit_table_date' => 'Дата активності',
'audit_date_from' => 'Діапазон дат від',
'audit_date_to' => 'Діапазон дат до',
'role_details' => 'Деталі ролі',
'role_name' => 'Назва ролі',
'role_desc' => 'Короткий опис ролі',
+ 'role_mfa_enforced' => 'Requires Multi-Factor Authentication',
'role_external_auth_id' => 'Зовнішні ID автентифікації',
'role_system' => 'Системні дозволи',
'role_manage_users' => 'Керування користувачами',
'role_manage_page_templates' => 'Управління шаблонами сторінок',
'role_access_api' => 'Доступ до системного API',
'role_manage_settings' => 'Керування налаштуваннями програми',
+ 'role_export_content' => 'Export content',
'role_asset' => 'Дозволи',
'roles_system_warning' => 'Майте на увазі, що доступ до будь-якого з вищезазначених трьох дозволів може дозволити користувачеві змінювати власні привілеї або привілеї інших в системі. Ролі з цими дозволами призначайте лише довіреним користувачам.',
'role_asset_desc' => 'Ці дозволи контролюють стандартні доступи всередині системи. Права на книги, розділи та сторінки перевизначать ці дозволи.',
'users_api_tokens_create' => 'Створити токен',
'users_api_tokens_expires' => 'Закінчується',
'users_api_tokens_docs' => 'Документація API',
+ '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',
// API Tokens
'user_api_token_create' => 'Створити токен API',
'it' => 'Italian',
'ja' => '日本語',
'ko' => '한국어',
+ 'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nl' => 'Nederlands',
'nb' => 'Norsk (Bokmål)',
'alpha_dash' => 'Поле :attribute має містити лише літери, цифри, дефіси та підкреслення.',
'alpha_num' => 'Поле :attribute має містити лише літери та цифри.',
'array' => 'Поле :attribute має бути масивом.',
+ 'backup_codes' => 'The provided code is not valid or has already been used.',
'before' => 'Поле :attribute має містити дату не пізніше :date.',
'between' => [
'numeric' => 'Поле :attribute має бути між :min та :max.',
],
'string' => 'Поле :attribute повинне містити текст.',
'timezone' => 'Поле :attribute повинне містити коректну часову зону.',
+ 'totp' => 'The provided code is not valid or has expired.',
'unique' => 'Вказане значення поля :attribute вже існує.',
'url' => 'Формат поля :attribute неправильний.',
'uploaded' => 'Не вдалося завантажити файл. Сервер може не приймати файли такого розміру.',
'bookshelf_delete_notification' => 'Giá sách đã được xóa thành công',
// Favourites
- 'favourite_add_notification' => '":name" has been added to your favourites',
- 'favourite_remove_notification' => '":name" has been removed from your favourites',
+ 'favourite_add_notification' => '":name" đã được thêm vào danh sách yêu thích của bạn',
+ 'favourite_remove_notification' => '":name" đã được gỡ khỏi danh sách yêu thích của bạn',
+
+ // MFA
+ 'mfa_setup_method_notification' => 'Cấu hình xác thực nhiều bước thành công',
+ 'mfa_remove_method_notification' => 'Đã gỡ xác thực nhiều bước',
// Other
'commented_on' => 'đã bình luận về',
'user_invite_page_welcome' => 'Chào mừng đến với :appName!',
'user_invite_page_text' => 'Để hoàn tất tài khoản và lấy quyền truy cập bạn cần đặt mật khẩu để sử dụng cho các lần đăng nhập sắp tới tại :appName.',
'user_invite_page_confirm_button' => 'Xác nhận Mật khẩu',
- 'user_invite_success' => 'Mật khẩu đã được thiết lập, bạn có quyền truy cập đến :appName!'
+ 'user_invite_success' => 'Mật khẩu đã được thiết lập, bạn có quyền truy cập đến :appName!',
+
+ // Multi-factor Authentication
+ 'mfa_setup' => 'Cài đặt xác thực nhiều bước',
+ 'mfa_setup_desc' => 'Cài đặt xác thực nhiều bước như một lớp bảo mật khác cho tài khoản của bạn.',
+ 'mfa_setup_configured' => 'Đã cài đặt',
+ 'mfa_setup_reconfigure' => 'Cài đặt lại',
+ 'mfa_setup_remove_confirmation' => 'Bạn có chắc muốn gỡ bỏ phương thức xác thực nhiều bước này?',
+ 'mfa_setup_action' => 'Cài đặt',
+ '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' => 'Ứng dụng di động',
+ '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' => 'Mã dự phòng',
+ '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' => 'Xác nhận và Mở',
+ 'mfa_gen_backup_codes_title' => 'Cài đặt Mã dự phòng',
+ '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' => 'Tải mã',
+ 'mfa_gen_backup_codes_usage_warning' => 'Mỗi mã chỉ có thể sử dụng một lần',
+ 'mfa_gen_totp_title' => 'Cài đặt ứng dụng di động',
+ 'mfa_gen_totp_desc' => 'Để sử dụng xác thực nhiều bước, bạn cần một ứng dụng di động hỗ trợ TOTP ví dụ như Google Authenticator, Authy hoặc Microsoft Authenticator.',
+ 'mfa_gen_totp_scan' => 'Quét mã QR dưới đây bằng ứng dụng xác thực mà bạn muốn để bắt đầu.',
+ 'mfa_gen_totp_verify_setup' => 'Xác nhận cài đặt',
+ 'mfa_gen_totp_verify_setup_desc' => 'Xác nhận rằng tất cả hoạt động bằng cách nhập vào một mã, được tạo ra bởi ứng dụng xác thực của bạn vào ô dưới đây:',
+ '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' => 'Mã dự phòng',
+ 'mfa_verify_backup_code_desc' => 'Nhập một trong các mã dự phòng còn lại của bạn vào ô phía dưới:',
+ 'mfa_verify_backup_code_enter_here' => 'Nhập mã xác thực của bạn tại đây',
+ 'mfa_verify_totp_desc' => 'Nhập mã do ứng dụng di động của bạn tạo ra vào dưới đây:',
+ 'mfa_setup_login_notification' => 'Đã cài đặt xác thực nhiều bước, bạn vui lòng đăng nhập lại sử dụng phương thức đã cài đặt.',
];
\ No newline at end of file
'reset' => 'Thiết lập lại',
'remove' => 'Xóa bỏ',
'add' => 'Thêm',
+ 'configure' => 'Cấu hình',
'fullscreen' => 'Toàn màn hình',
- 'favourite' => 'Favourite',
- 'unfavourite' => 'Unfavourite',
- 'next' => 'Next',
- 'previous' => 'Previous',
+ 'favourite' => 'Yêu thích',
+ 'unfavourite' => 'Bỏ yêu thích',
+ 'next' => 'Tiếp theo',
+ 'previous' => 'Trước đó',
// Sort Options
'sort_options' => 'Tùy Chọn Sắp Xếp',
'sort_ascending' => 'Sắp xếp tăng dần',
'sort_descending' => 'Sắp xếp giảm dần',
'sort_name' => 'Tên',
- 'sort_default' => 'Default',
+ 'sort_default' => 'Mặc định',
'sort_created_at' => 'Ngày Tạo',
'sort_updated_at' => 'Ngày cập nhật',
'no_activity' => 'Không có hoạt động nào',
'no_items' => 'Không có mục nào khả dụng',
'back_to_top' => 'Lên đầu trang',
+ 'skip_to_main_content' => 'Nhảy đến nội dung chính',
'toggle_details' => 'Bật/tắt chi tiết',
'toggle_thumbnails' => 'Bật/tắt ảnh ảnh nhỏ',
'details' => 'Chi tiết',
// Footer Link Options
// Not directly used but available for convenience to users.
- 'privacy_policy' => 'Privacy Policy',
- 'terms_of_service' => 'Terms of Service',
+ 'privacy_policy' => 'Chính Sách Quyền Riêng Tư',
+ 'terms_of_service' => 'Điều khoản Dịch vụ',
];
'meta_created_name' => 'Được tạo :timeLength bởi :user',
'meta_updated' => 'Được cập nhật :timeLength',
'meta_updated_name' => 'Được cập nhật :timeLength bởi :user',
- 'meta_owned_name' => 'Owned by :user',
+ 'meta_owned_name' => 'Được sở hữu bởi :user',
'entity_select' => 'Chọn thực thể',
'images' => 'Ảnh',
'my_recent_drafts' => 'Bản nháp gần đây của tôi',
'my_recently_viewed' => 'Xem gần đây',
'my_most_viewed_favourites' => 'My Most Viewed Favourites',
- 'my_favourites' => 'My Favourites',
+ 'my_favourites' => 'Danh sách yêu thích của tôi',
'no_pages_viewed' => 'Bạn chưa xem bất cứ trang nào',
'no_pages_recently_created' => 'Không có trang nào được tạo gần đây',
'no_pages_recently_updated' => 'Không có trang nào được cập nhật gần đây',
'export_html' => 'Đang chứa tệp tin Web',
'export_pdf' => 'Tệp PDF',
'export_text' => 'Tệp văn bản thuần túy',
+ 'export_md' => '\bTệp Markdown',
// 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_save' => 'Lưu quyền hạn',
- 'permissions_owner' => 'Owner',
+ 'permissions_owner' => 'Chủ sở hữu',
// Search
'search_results' => 'Kết quả Tìm kiếm',
'search_permissions_set' => 'Phân quyền',
'search_created_by_me' => 'Được tạo bởi tôi',
'search_updated_by_me' => 'Được cập nhật bởi tôi',
- 'search_owned_by_me' => 'Owned by me',
+ 'search_owned_by_me' => 'Của tôi',
'search_date_options' => 'Tùy chọn ngày',
'search_updated_before' => 'Đã được cập nhật trước đó',
'search_updated_after' => 'Đã được cập nhật sau',
'shelves_permissions' => 'Các quyền đối với kệ sách',
'shelves_permissions_updated' => 'Các quyền với kệ sách đã được cập nhật',
'shelves_permissions_active' => 'Đang bật các quyền hạn từ Kệ sách',
+ 'shelves_permissions_cascade_warning' => 'Các quyền trên giá sách sẽ không được tự động gán cho các sách trên đó. Vì một quyển sách có thể tồn tại trên nhiều giá sách. Các quyền có thể được sao chép xuống các quyển sách sử dụng tuỳ chọn dưới đây.',
'shelves_copy_permissions_to_books' => 'Sao chép các quyền cho sách',
'shelves_copy_permissions' => 'Sao chép các quyền',
'shelves_copy_permissions_explain' => 'Điều này sẽ áp dụng các cài đặt quyền của giá sách hiện tại với tất cả các cuốn sách bên trong. Trước khi kích hoạt, đảm bảo bất cứ thay đổi liên quan đến quyền của giá sách này đã được lưu.',
'chapters_create' => 'Tạo Chương mới',
'chapters_delete' => 'Xóa Chương',
'chapters_delete_named' => 'Xóa Chương :chapterName',
- 'chapters_delete_explain' => 'This will delete the chapter with the name \':chapterName\'. All pages that exist within this chapter will also be deleted.',
+ 'chapters_delete_explain' => 'Hành động này sẽ xoá chương \':chapterName\'. Tất cả các trang trong chương này cũng sẽ bị xoá.',
'chapters_delete_confirm' => 'Bạn có chắc chắn muốn xóa chương này?',
'chapters_edit' => 'Sửa Chương',
'chapters_edit_named' => 'Sửa chương :chapterName',
'pages_revisions' => 'Phiên bản Trang',
'pages_revisions_named' => 'Phiên bản Trang cho :pageName',
'pages_revision_named' => 'Phiên bản Trang cho :pageName',
- 'pages_revision_restored_from' => 'Restored from #:id; :summary',
+ 'pages_revision_restored_from' => 'Khôi phục từ #:id; :summary',
'pages_revisions_created_by' => 'Tạo bởi',
'pages_revisions_date' => 'Ngày của Phiên bản',
'pages_revisions_number' => '#',
'404_page_not_found' => 'Không Tìm Thấy Trang',
'sorry_page_not_found' => 'Xin lỗi, Không tìm thấy trang bạn đang tìm kiếm.',
'sorry_page_not_found_permission_warning' => 'Nếu trang bạn tìm kiếm tồn tại, có thể bạn đang không có quyền truy cập.',
- 'image_not_found' => 'Image Not Found',
- 'image_not_found_subtitle' => 'Sorry, The image file you were looking for could not be found.',
- 'image_not_found_details' => 'If you expected this image to exist it might have been deleted.',
+ 'image_not_found' => 'Không tìm thấy Ảnh',
+ 'image_not_found_subtitle' => 'Rất tiếc, không thể tìm thấy Ảnh bạn đang tìm kiếm.',
+ 'image_not_found_details' => 'Nếu bạn hi vọng ảnh này tồn tại, rất có thể nó đã bị xóa.',
'return_home' => 'Quay lại trang chủ',
'error_occurred' => 'Đã xảy ra lỗi',
'app_down' => ':appName hiện đang ngoại tuyến',
'recycle_bin' => 'Thùng Rác',
'recycle_bin_desc' => 'Here you can restore items that have been deleted or choose to permanently remove them from the system. This list is unfiltered unlike similar activity lists in the system where permission filters are applied.',
'recycle_bin_deleted_item' => 'Mục Đã Xóa',
+ 'recycle_bin_deleted_parent' => 'Parent',
'recycle_bin_deleted_by' => 'Xóa Bởi',
'recycle_bin_deleted_at' => 'Thời điểm Xóa',
'recycle_bin_permanently_delete' => 'Xóa Vĩnh viễn',
'recycle_bin_restore_list' => 'Items to be Restored',
'recycle_bin_restore_confirm' => 'This action will restore the deleted item, including any child elements, to their original location. If the original location has since been deleted, and is now in the recycle bin, the parent item will also need to be restored.',
'recycle_bin_restore_deleted_parent' => 'The parent of this item has also been deleted. These will remain deleted until that parent is also restored.',
+ 'recycle_bin_restore_parent' => 'Restore Parent',
'recycle_bin_destroy_notification' => 'Deleted :count total items from the recycle bin.',
'recycle_bin_restore_notification' => 'Restored :count total items from the recycle bin.',
'audit_table_user' => 'Người dùng',
'audit_table_event' => 'Sự kiện',
'audit_table_related' => 'Related Item or Detail',
+ 'audit_table_ip' => 'IP Address',
'audit_table_date' => 'Ngày hoạt động',
'audit_date_from' => 'Ngày từ khoảng',
'audit_date_to' => 'Ngày đến khoảng',
'role_details' => 'Thông tin chi tiết Quyền',
'role_name' => 'Tên quyền',
'role_desc' => 'Thông tin vắn tắt của Quyền',
+ 'role_mfa_enforced' => 'Requires Multi-Factor Authentication',
'role_external_auth_id' => 'Mã của xác thực ngoài',
'role_system' => 'Quyền Hệ thống',
'role_manage_users' => 'Quản lý người dùng',
'role_manage_page_templates' => 'Quản lý các mẫu trang',
'role_access_api' => 'Truy cập đến API hệ thống',
'role_manage_settings' => 'Quản lý cài đặt của ứng dụng',
+ 'role_export_content' => 'Export content',
'role_asset' => 'Quyền tài sản (asset)',
'roles_system_warning' => 'Be aware that access to any of the above three permissions can allow a user to alter their own privileges or the privileges of others in the system. Only assign roles with these permissions to trusted users.',
'role_asset_desc' => 'Các quyền này điều khiển truy cập mặc định tới tài sản (asset) nằm trong hệ thống. Quyền tại Sách, Chường và Trang se ghi đè các quyền này.',
'users_api_tokens_create' => 'Tạo Token',
'users_api_tokens_expires' => 'Hết hạn',
'users_api_tokens_docs' => 'Tài liệu API',
+ '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',
// API Tokens
'user_api_token_create' => 'Tạo Token API',
'it' => 'Italian',
'ja' => '日本語',
'ko' => '한국어',
+ 'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nl' => 'Nederlands',
'nb' => 'Norsk (Bokmål)',
'alpha_dash' => ':attribute chỉ được chứa chữ cái, chữ số, gạch nối và gạch dưới.',
'alpha_num' => ':attribute chỉ được chứa chữ cái hoặc chữ số.',
'array' => ':attribute phải là một mảng.',
+ 'backup_codes' => 'The provided code is not valid or has already been used.',
'before' => ':attribute phải là một ngày trước :date.',
'between' => [
'numeric' => ':attribute phải nằm trong khoảng :min đến :max.',
],
'string' => ':attribute phải là một chuỗi.',
'timezone' => ':attribute phải là một khu vực hợp lệ.',
+ 'totp' => 'The provided code is not valid or has expired.',
'unique' => ':attribute đã có người sử dụng.',
'url' => 'Định dạng của :attribute không hợp lệ.',
'uploaded' => 'Tệp tin đã không được tải lên. Máy chủ không chấp nhận các tệp tin với dung lượng lớn như tệp tin trên.',
'favourite_add_notification' => '":name" 已添加到你的收藏',
'favourite_remove_notification' => '":name" 已从你的收藏中删除',
+ // MFA
+ 'mfa_setup_method_notification' => '多重身份认证设置成功',
+ 'mfa_remove_method_notification' => '多重身份认证已成功移除',
+
// Other
'commented_on' => '评论',
'permissions_update' => '权限已更新',
'remember_me' => '记住我',
'ldap_email_hint' => '请输入用于此帐户的电子邮件。',
'create_account' => '创建账户',
- 'already_have_account' => '您已经有账号?',
+ 'already_have_account' => '已经有账号了?',
'dont_have_account' => '您还没有账号吗?',
'social_login' => 'SNS登录',
'social_registration' => '使用社交网站账号注册',
'user_invite_page_welcome' => '欢迎来到 :appName!',
'user_invite_page_text' => '要完成您的帐户并获得访问权限,您需要设置一个密码,该密码将在以后访问时用于登录 :appName。',
'user_invite_page_confirm_button' => '确认密码',
- 'user_invite_success' => '已设置密码,您现在可以访问 :appName!'
+ 'user_invite_success' => '已设置密码,您现在可以访问 :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' => '移动设备 App',
+ 'mfa_option_totp_desc' => '要使用多重身份认证功能,您需要一个支持 TOTP(基于时间的一次性密码算法) 的移动设备 App,如谷歌身份验证器(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' => '移动设备 App',
+ 'mfa_gen_totp_desc' => '要使用多重身份认证功能,您需要一个支持 TOTP(基于时间的一次性密码算法) 的移动设备 App,如谷歌身份验证器(Google Authenticator)、Authy 或微软身份验证器(Microsoft Authenticator)。',
+ 'mfa_gen_totp_scan' => '要开始操作,请使用你的身份验证 App 扫描下面的二维码。',
+ 'mfa_gen_totp_verify_setup' => '验证设置',
+ 'mfa_gen_totp_verify_setup_desc' => '请在下面的框中输入您在身份验证 App 中生成的认证码来验证一切是否正常:',
+ 'mfa_gen_totp_provide_code_here' => '在此输入您的 App 生成的认证码',
+ 'mfa_verify_access' => '认证访问',
+ 'mfa_verify_access_desc' => '您的账户要求您在访问前通过额外的验证确认您的身份。使用您设置的认证方法认证以继续。',
+ 'mfa_verify_no_methods' => '没有设置认证方法',
+ 'mfa_verify_no_methods_desc' => '您的账户没有设置多重身份认证。您需要至少设置一种才能访问。',
+ 'mfa_verify_use_totp' => '使用移动设备 App 进行认证',
+ 'mfa_verify_use_backup_codes' => '使用备用认证码进行认证',
+ 'mfa_verify_backup_code' => '备用认证码',
+ 'mfa_verify_backup_code_desc' => '在下面输入你的其中一个备用认证码:',
+ 'mfa_verify_backup_code_enter_here' => '在这里输入备用认证码',
+ 'mfa_verify_totp_desc' => '在下面输入您的移动 App 生成的认证码:',
+ 'mfa_setup_login_notification' => '多重身份认证已设置,请使用新配置的方法重新登录。',
];
\ No newline at end of file
'reset' => '重置',
'remove' => '删除',
'add' => '添加',
+ 'configure' => '配置',
'fullscreen' => '全屏',
'favourite' => '收藏',
- 'unfavourite' => '不喜欢',
+ 'unfavourite' => '取消收藏',
'next' => '下一页',
'previous' => '上一页',
'no_activity' => '没有活动要显示',
'no_items' => '没有可用的项目',
'back_to_top' => '回到顶部',
+ 'skip_to_main_content' => '跳转到主要内容',
'toggle_details' => '显示/隐藏详细信息',
'toggle_thumbnails' => '显示/隐藏缩略图',
'details' => '详细信息',
'export_html' => '网页文件',
'export_pdf' => 'PDF文件',
'export_text' => '纯文本文件',
+ 'export_md' => 'Markdown 文件',
// Permissions and restrictions
'permissions' => '权限',
'shelves_permissions' => '书架权限',
'shelves_permissions_updated' => '书架权限已更新',
'shelves_permissions_active' => '书架权限激活',
+ 'shelves_permissions_cascade_warning' => '书架上的权限不会自动应用到书架里的书。这是因为书可以在多个书架上存在。使用下面的选项可以将权限复制到书架里的书上。',
'shelves_copy_permissions_to_books' => '将权限复制到图书',
'shelves_copy_permissions' => '复制权限',
'shelves_copy_permissions_explain' => '这会将此书架的当前权限设置应用于其中包含的所有图书。 在激活之前,请确保已保存对此书架权限的任何更改。',
// Books
'book' => '图书',
'books' => '图书',
- 'x_books' => ':count本书',
+ 'x_books' => ':count 本书',
'books_empty' => '不存在已创建的书',
'books_popular' => '热门图书',
'books_recent' => '最近的书',
'app_customization' => '定制',
'app_features_security' => '功能与安全',
'app_name' => '站点名称',
- 'app_name_desc' => '此名称将在网页头部和Email中显示。',
+ 'app_name_desc' => '此名称将在网页头部和系统发送的电子邮件中显示。',
'app_name_header' => '在网页头部显示站点名称?',
'app_public_access' => '访问权限',
'app_public_access_desc' => '启用此选项将允许未登录的用户访问站点内容。',
'app_primary_color' => '站点主色',
'app_primary_color_desc' => '这应该是一个十六进制值。<br>保留为空以重置为默认颜色。',
'app_homepage' => '站点主页',
- 'app_homepage_desc' => '选择要在主页上显示的页面来替换默认的视图,选定页面的访问权限将被忽略。',
+ 'app_homepage_desc' => '选择要在主页上显示的页面来替换默认的页面,选定页面的访问权限将被忽略。',
'app_homepage_select' => '选择一个页面',
'app_footer_links' => '页脚链接',
'app_footer_links_desc' => '添加在网站页脚中显示的链接。这些链接将显示在大多数页面的底部,也包括不需要登录的页面。您可以使用标签"trans::<key>"来使用系统定义的翻译。例如:使用"trans::common.privacy_policy"将显示为“隐私政策”,而"trans::common.terms_of_service"将显示为“服务条款”。',
'reg_enable_external_warning' => '当启用外部LDAP或者SAML认证时,上面的选项会被忽略。当使用外部系统认证认证成功时,将自动创建非现有会员的用户账户。',
'reg_email_confirmation' => '邮件确认',
'reg_email_confirmation_toggle' => '需要电子邮件确认',
- 'reg_confirm_email_desc' => '如果使用域名限制,则需要Email验证,并且该值将被忽略。',
+ 'reg_confirm_email_desc' => '如果使用域名限制,则需要电子邮件验证,并且该值将被忽略。',
'reg_confirm_restrict_domain' => '域名限制',
- 'reg_confirm_restrict_domain_desc' => '输入您想要限制注册的Email域名列表,用逗号隔开。在被允许与应用程序交互之前,用户将被发送一封Email来确认他们的地址。<br>注意用户在注册成功后可以修改他们的Email地址。',
+ 'reg_confirm_restrict_domain_desc' => '输入您想要限制注册的电子邮件域名列表(即只允许使用这些电子邮件域名注册),多个域名用英文逗号隔开。在允许用户与应用程序交互之前,系统将向用户发送一封电子邮件以确认其电子邮件地址。<br>请注意,用户在注册成功后仍然可以更改他们的电子邮件地址。',
'reg_confirm_restrict_domain_placeholder' => '尚未设置限制',
// Maintenance settings
'maint' => '维护',
'maint_image_cleanup' => '清理图像',
- 'maint_image_cleanup_desc' => "扫描页面和修订内容以检查哪些图像是正在使用的以及哪些图像是多余的。确保在运行前创建完整的数据库和映像备份。",
+ 'maint_image_cleanup_desc' => "扫描页面和修订内容以检查哪些图片是正在使用的以及哪些图片是多余的。确保在运行前完整备份数据库和图片。",
'maint_delete_images_only_in_revisions' => '同时删除只存在于旧的页面修订中的图片',
'maint_image_cleanup_run' => '运行清理',
'maint_image_cleanup_warning' => '发现了 :count 张可能未使用的图像。您确定要删除这些图像吗?',
'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_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_table_user' => '用户',
'audit_table_event' => '事件',
'audit_table_related' => '相关项目或详细信息',
+ 'audit_table_ip' => 'IP地址',
'audit_table_date' => '活动日期',
'audit_date_from' => '日期范围从',
'audit_date_to' => '日期范围至',
'role_details' => '角色详细信息',
'role_name' => '角色名',
'role_desc' => '角色简述',
+ 'role_mfa_enforced' => '需要多重身份认证',
'role_external_auth_id' => '外部身份认证ID',
'role_system' => '系统权限',
'role_manage_users' => '管理用户',
'role_manage_page_templates' => '管理页面模板',
'role_access_api' => '访问系统 API',
'role_manage_settings' => '管理App设置',
+ 'role_export_content' => '导出内容',
'role_asset' => '资源许可',
'roles_system_warning' => '请注意,具有上述三个权限中的任何一个都可以允许用户更改自己的特权或系统中其他人的特权。 只将具有这些权限的角色分配给受信任的用户。',
'role_asset_desc' => '对系统内资源的默认访问许可将由这些权限控制。单独设置在书籍,章节和页面上的权限将覆盖这里的权限设定。',
'users_api_tokens_create' => '创建令牌',
'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 令牌',
'it' => 'Italian',
'ja' => '日本語',
'ko' => '한국어',
+ 'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nl' => 'Nederlands',
'nb' => '挪威语 (Bokmål)',
'alpha_dash' => ':attribute 只能包含字母、数字和短横线。',
'alpha_num' => ':attribute 只能包含字母和数字。',
'array' => ':attribute 必须是一个数组。',
+ 'backup_codes' => '您输入的认证码无效或已被使用。',
'before' => ':attribute 必须是在 :date 前的日期。',
'between' => [
'numeric' => ':attribute 必须在:min到:max之间。',
],
'string' => ':attribute 必须是字符串。',
'timezone' => ':attribute 必须是有效的区域。',
+ 'totp' => '您输入的认证码无效或已过期。',
'unique' => ':attribute 已经被使用。',
'url' => ':attribute 格式无效。',
'uploaded' => '无法上传文件。 服务器可能不接受此大小的文件。',
'bookshelf_delete_notification' => '書架已刪除成功',
// Favourites
- 'favourite_add_notification' => '":name" has been added to your favourites',
- 'favourite_remove_notification' => '":name" has been removed from your favourites',
+ 'favourite_add_notification' => '":name" 已加入到你的最愛',
+ 'favourite_remove_notification' => '":name" 已從你的最愛移除',
+
+ // MFA
+ 'mfa_setup_method_notification' => 'Multi-factor method successfully configured',
+ 'mfa_remove_method_notification' => 'Multi-factor method successfully removed',
// Other
'commented_on' => '評論',
'user_invite_page_welcome' => '歡迎使用 :appName!',
'user_invite_page_text' => '要完成設定您的帳號並取得存取權,您必須設定密碼,此密碼將用於登入 :appName。',
'user_invite_page_confirm_button' => '確認密碼',
- 'user_invite_success' => '密碼已設定,您現在可以存取 :appName 了!'
+ 'user_invite_success' => '密碼已設定,您現在可以存取 :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_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.',
];
\ No newline at end of file
'reset' => '重設',
'remove' => '移除',
'add' => '新增',
+ 'configure' => 'Configure',
'fullscreen' => '全螢幕',
- 'favourite' => 'Favourite',
- 'unfavourite' => 'Unfavourite',
- 'next' => 'Next',
- 'previous' => 'Previous',
+ 'favourite' => '最愛',
+ 'unfavourite' => '取消最愛',
+ 'next' => '下一頁',
+ 'previous' => '上一頁',
// Sort Options
'sort_options' => '排序選項',
'sort_ascending' => '遞增排序',
'sort_descending' => '遞減排序',
'sort_name' => '名稱',
- 'sort_default' => 'Default',
+ 'sort_default' => '預設',
'sort_created_at' => '建立日期',
'sort_updated_at' => '更新日期',
'no_activity' => '無活動可顯示',
'no_items' => '無可用項目',
'back_to_top' => '回到頂端',
+ 'skip_to_main_content' => '跳到主內容',
'toggle_details' => '顯示/隱藏詳細資訊',
'toggle_thumbnails' => '顯示/隱藏縮圖',
'details' => '詳細資訊',
'breadcrumb' => '頁面路徑',
// Header
- 'header_menu_expand' => 'Expand Header Menu',
+ 'header_menu_expand' => '展開選單',
'profile_menu' => '個人資料選單',
'view_profile' => '檢視個人資料',
'edit_profile' => '編輯個人資料',
// Layout tabs
'tab_info' => '資訊',
- 'tab_info_label' => 'Tab: Show Secondary Information',
+ 'tab_info_label' => '顯示次要訊息',
'tab_content' => '內容',
- 'tab_content_label' => 'Tab: Show Primary Content',
+ 'tab_content_label' => '顯示主要內容',
// Email Content
'email_action_help' => '如果您無法點擊 ":actionText" 按鈕,請將下方的網址複製並貼上到您的網路瀏覽器中:',
'images' => '圖片',
'my_recent_drafts' => '我最近的草稿',
'my_recently_viewed' => '我最近檢視',
- 'my_most_viewed_favourites' => 'My Most Viewed Favourites',
- 'my_favourites' => 'My Favourites',
+ 'my_most_viewed_favourites' => '我瀏覽最多次的最愛',
+ 'my_favourites' => '我的最愛',
'no_pages_viewed' => '您尚未看過任何頁面',
'no_pages_recently_created' => '最近未建立任何頁面',
'no_pages_recently_updated' => '最近沒有頁面被更新',
'export_html' => '網頁檔案',
'export_pdf' => 'PDF 檔案',
'export_text' => '純文字檔案',
+ 'export_md' => 'Markdown 檔案',
// Permissions and restrictions
'permissions' => '權限',
'search_permissions_set' => '權限設定',
'search_created_by_me' => '我建立的',
'search_updated_by_me' => '我更新的',
- 'search_owned_by_me' => 'Owned by me',
+ 'search_owned_by_me' => '我所擁有的',
'search_date_options' => '日期選項',
'search_updated_before' => '在此之前更新',
'search_updated_after' => '在此之後更新',
'shelves_permissions' => '書架權限',
'shelves_permissions_updated' => '書架權限已更新',
'shelves_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' => '這會將此書架目前的權限設定套用到所有包含的書本上。在啟用前,請確認您已儲存任何對此書架權限的變更。',
'404_page_not_found' => '找不到頁面',
'sorry_page_not_found' => '抱歉,找不到您在尋找的頁面。',
'sorry_page_not_found_permission_warning' => '如果您確認這個頁面存在,則代表可能沒有查看它的權限。',
- 'image_not_found' => 'Image Not Found',
- 'image_not_found_subtitle' => 'Sorry, The image file you were looking for could not be found.',
- 'image_not_found_details' => 'If you expected this image to exist it might have been deleted.',
+ 'image_not_found' => '找不到圖片',
+ 'image_not_found_subtitle' => '對不起,無法找到您所看的圖片',
+ 'image_not_found_details' => '原本的圖片可能已經被刪除',
'return_home' => '回到首頁',
'error_occurred' => '發生錯誤',
'app_down' => ':appName 離線中',
'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_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_table_user' => '使用者',
'audit_table_event' => '活動',
'audit_table_related' => '相關的項目或詳細資訊',
+ 'audit_table_ip' => 'IP Address',
'audit_table_date' => '活動日期',
'audit_date_from' => '日期範圍,從',
'audit_date_to' => '日期範圍,到',
'role_details' => '角色詳細資訊',
'role_name' => '角色名稱',
'role_desc' => '角色簡短說明',
+ 'role_mfa_enforced' => 'Requires Multi-Factor Authentication',
'role_external_auth_id' => '外部身份驗證 ID',
'role_system' => '系統權限',
'role_manage_users' => '管理使用者',
'role_manage_page_templates' => '管理頁面範本',
'role_access_api' => '存取系統 API',
'role_manage_settings' => '管理應用程式設定',
+ 'role_export_content' => 'Export content',
'role_asset' => '資源權限',
'roles_system_warning' => '請注意,有上述三項權限中的任一項的使用者都可以更改自己或系統中其他人的權限。有這些權限的角色只應分配給受信任的使用者。',
'role_asset_desc' => '對系統內資源的預設權限將由這裡的權限控制。若有單獨設定在書本、章節和頁面上的權限,將會覆寫這裡的權限設定。',
'users_api_tokens_create' => '建立權杖',
'users_api_tokens_expires' => '過期',
'users_api_tokens_docs' => 'API 文件',
+ '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',
// API Tokens
'user_api_token_create' => '建立 API 權杖',
'it' => 'Italian',
'ja' => '日本語',
'ko' => '한국어',
+ 'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nl' => 'Nederlands',
'nb' => 'Norsk (Bokmål)',
'alpha_dash' => ':attribute 只能包含字母、數字、破折號與底線。',
'alpha_num' => ':attribute 只能包含字母和數字。',
'array' => ':attribute 必須是陣列。',
+ 'backup_codes' => 'The provided code is not valid or has already been used.',
'before' => ':attribute 必須是在 :date 前的日期。',
'between' => [
'numeric' => ':attribute 必須在 :min 到 :max 之間。',
],
'string' => ':attribute 必須是字元串。',
'timezone' => ':attribute 必須是有效的區域。',
+ 'totp' => 'The provided code is not valid or has expired.',
'unique' => ':attribute 已經被使用。',
'url' => ':attribute 格式無效。',
'uploaded' => '無法上傳文件, 服務器可能不接受此大小的文件。',
}
}
+.input-fill-width {
+ width: 100% !important;
+}
+
.fake-input {
@extend .input-base;
overflow: auto;
.markdown-editor-display {
background-color: #fff;
body {
+ display: block;
background-color: #fff;
padding-inline-start: 16px;
padding-inline-end: 16px;
.flex {
min-height: 0;
flex: 1;
+ max-width: 100%;
&.fit-content {
flex-basis: auto;
flex-grow: 0;
display: inline-block !important;
}
+.relative {
+ position: relative;
+}
+
.hidden {
display: none !important;
}
}
}
ul {
- padding-left: $-m * 1.3;
- padding-right: $-m * 1.3;
list-style: disc;
ul {
list-style: circle;
- margin-top: 0;
- margin-bottom: 0;
}
label {
margin: 0;
ol {
list-style: decimal;
- padding-left: $-m * 2;
- padding-right: $-m * 2;
+}
+
+ol, ul {
+ padding-left: $-m * 2.0;
+ padding-right: $-m * 2.0;
+}
+
+li > ol, li > ul {
+ margin-top: 0;
+ margin-bottom: 0;
+ margin-block-end: 0;
+ margin-block-start: 0;
+ padding-block-end: 0;
+ padding-block-start: 0;
+ padding-left: $-m * 1.2;
+ padding-right: $-m * 1.2;
}
li.checkbox-item, li.task-list-item {
list-style: none;
- margin-left: - ($-m * 1.3);
+ margin-left: -($-m * 1.2);
input[type="checkbox"] {
margin-right: $-xs;
}
-}
-
-li > ol, li > ul {
- margin-block-end: 0px;
- margin-block-start: 0px;
- padding-block-end: 0px;
- padding-block-start: 0px;
+ li.checkbox-item, li.task-list-item {
+ margin-left: $-xs;
+ }
}
/*
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div style="overflow: auto;">
<section code-highlighter class="card content-wrap auto-height">
- <h1 class="list-heading text-capitals mb-l">Getting Started</h1>
-
- <h5 id="authentication" class="text-mono mb-m">Authentication</h5>
- <p>
- To access the API a user has to have the <em>"Access System API"</em> permission enabled on one of their assigned roles.
- Permissions to content accessed via the API is limited by the roles & permissions assigned to the user that's used to access the API.
- </p>
- <p>Authentication to use the API is primarily done using API Tokens. Once the <em>"Access System API"</em> permission has been assigned to a user, a "API Tokens" section should be visible when editing their user profile. Choose "Create Token" and enter an appropriate name and expiry date, relevant for your API usage then press "Save". A "Token ID" and "Token Secret" will be immediately displayed. These values should be used as a header in API HTTP requests in the following format:</p>
- <pre><code class="language-css">Authorization: Token <token_id>:<token_secret></code></pre>
- <p>Here's an example of an authorized cURL request to list books in the system:</p>
- <pre><code class="language-shell">curl --request GET \
- --url https://example.com/api/books \
- --header 'Authorization: Token C6mdvEQTGnebsmVn3sFNeeuelGEBjyQp:NOvD3VlzuSVuBPNaf1xWHmy7nIRlaj22'</code></pre>
- <p>If already logged into the system within the browser, via a user account with permission to access the API, the system will also accept an existing session meaning you can browse API endpoints directly in the browser or use the browser devtools to play with the API.</p>
-
- <hr>
-
- <h5 id="request-format" class="text-mono mb-m">Request Format</h5>
- <p>The API is primarily design to be interfaced using JSON so the majority of API endpoints, that accept data, will read JSON request data although <code>application/x-www-form-urlencoded</code> request data is also accepted. Endpoints that receive file data will need data sent in a <code>multipart/form-data</code> format although this will be highlighted in the documentation for such endpoints.</p>
- <p>For endpoints in this documentation that accept data, a "Body Parameters" table will be available showing the parameters that will accepted in the request. Any rules for the values of such parameters, such as the data-type or if they're required, will be shown alongside the parameter name.</p>
-
- <hr>
-
- <h5 id="listing-endpoints" class="text-mono mb-m">Listing Endpoints</h5>
- <p>Some endpoints will return a list of data models. These endpoints will return an array of the model data under a <code>data</code> property along with a numeric <code>total</code> property to indicate the total number of records found for the query within the system. Here's an example of a listing response:</p>
- <pre><code class="language-json">{
- "data": [
- {
- "id": 1,
- "name": "BookStack User Guide",
- "slug": "bookstack-user-guide",
- "description": "This is a general guide on using BookStack on a day-to-day basis.",
- "created_at": "2019-05-05 21:48:46",
- "updated_at": "2019-12-11 20:57:31",
- "created_by": 1,
- "updated_by": 1,
- "image_id": 3
- }
- ],
- "total": 16
-}</code></pre>
- <p>
- There are a number of standard URL parameters that can be supplied to manipulate and page through the results returned from a listing endpoint:
- </p>
- <table class="table">
- <tr>
- <th>Parameter</th>
- <th>Details</th>
- <th width="30%">Examples</th>
- </tr>
- <tr>
- <td>count</td>
- <td>
- Specify how many records will be returned in the response. <br>
- (Default: {{ config('api.default_item_count') }}, Max: {{ config('api.max_item_count') }})
- </td>
- <td>Limit the count to 50<br><code>?count=50</code></td>
- </tr>
- <tr>
- <td>offset</td>
- <td>
- Specify how many records to skip over in the response. <br>
- (Default: 0)
- </td>
- <td>Skip over the first 100 records<br><code>?offset=100</code></td>
- </tr>
- <tr>
- <td>sort</td>
- <td>
- Specify what field is used to sort the data and the direction of the sort (Ascending or Descending).<br>
- Value is the name of a field, A <code>+</code> or <code>-</code> prefix dictates ordering. <br>
- Direction defaults to ascending. <br>
- Can use most fields shown in the response.
- </td>
- <td>
- Sort by name ascending<br><code>?sort=+name</code> <br> <br>
- Sort by "Created At" date descending<br><code>?sort=-created_at</code>
- </td>
- </tr>
- <tr>
- <td>filter[<field>]</td>
- <td>
- Specify a filter to be applied to the query. Can use most fields shown in the response. <br>
- By default a filter will apply a "where equals" query but the below operations are available using the format filter[<field>:<operation>] <br>
- <table>
- <tr>
- <td>eq</td>
- <td>Where <code><field></code> equals the filter value.</td>
- </tr>
- <tr>
- <td>ne</td>
- <td>Where <code><field></code> does not equal the filter value.</td>
- </tr>
- <tr>
- <td>gt</td>
- <td>Where <code><field></code> is greater than the filter value.</td>
- </tr>
- <tr>
- <td>lt</td>
- <td>Where <code><field></code> is less than the filter value.</td>
- </tr>
- <tr>
- <td>gte</td>
- <td>Where <code><field></code> is greater than or equal to the filter value.</td>
- </tr>
- <tr>
- <td>lte</td>
- <td>Where <code><field></code> is less than or equal to the filter value.</td>
- </tr>
- <tr>
- <td>like</td>
- <td>
- Where <code><field></code> is "like" the filter value. <br>
- <code>%</code> symbols can be used as wildcards.
- </td>
- </tr>
- </table>
- </td>
- <td>
- Filter where id is 5: <br><code>?filter[id]=5</code><br><br>
- Filter where id is not 5: <br><code>?filter[id:ne]=5</code><br><br>
- Filter where name contains "cat": <br><code>?filter[name:like]=%cat%</code><br><br>
- Filter where created after 2020-01-01: <br><code>?filter[created_at:gt]=2020-01-01</code>
- </td>
- </tr>
- </table>
-
- <hr>
-
- <h5 id="error-handling" class="text-mono mb-m">Error Handling</h5>
- <p>
- Successful responses will return a 200 or 204 HTTP response code. Errors will return a 4xx or a 5xx HTTP response code depending on the type of error. Errors follow a standard format as shown below. The message provided may be translated depending on the configured language of the system in addition to the API users' language preference. The code provided in the JSON response will match the HTTP response code.
- </p>
-
- <pre><code class="language-json">{
- "error": {
- "code": 401,
- "message": "No authorization token found on the request"
- }
-}
-</code></pre>
-
+ @include('api-docs.parts.getting-started')
</section>
@foreach($docs as $model => $endpoints)
<h1 class="list-heading text-capitals">{{ $model }}</h1>
@foreach($endpoints as $endpoint)
- <h6 class="text-uppercase text-muted float right">{{ $endpoint['controller_method_kebab'] }}</h6>
- <h5 id="{{ $endpoint['name'] }}" class="text-mono mb-m">
- <span class="api-method" data-method="{{ $endpoint['method'] }}">{{ $endpoint['method'] }}</span>
- @if($endpoint['controller_method_kebab'] === 'list')
- <a style="color: inherit;" target="_blank" rel="noopener" href="{{ url($endpoint['uri']) }}">{{ url($endpoint['uri']) }}</a>
- @else
- {{ url($endpoint['uri']) }}
- @endif
- </h5>
- <p class="mb-m">{{ $endpoint['description'] ?? '' }}</p>
- @if($endpoint['body_params'] ?? false)
- <details class="mb-m">
- <summary class="text-muted">Body Parameters</summary>
- <table class="table">
- <tr>
- <th>Param Name</th>
- <th>Value Rules</th>
- </tr>
- @foreach($endpoint['body_params'] as $paramName => $rules)
- <tr>
- <td>{{ $paramName }}</td>
- <td>
- @foreach($rules as $rule)
- <code class="mr-xs">{{ $rule }}</code>
- @endforeach
- </td>
- </tr>
- @endforeach
- </table>
- </details>
- @endif
- @if($endpoint['example_request'] ?? false)
- <details details-highlighter class="mb-m">
- <summary class="text-muted">Example Request</summary>
- <pre><code class="language-json">{{ $endpoint['example_request'] }}</code></pre>
- </details>
- @endif
- @if($endpoint['example_response'] ?? false)
- <details details-highlighter class="mb-m">
- <summary class="text-muted">Example Response</summary>
- <pre><code class="language-json">{{ $endpoint['example_response'] }}</code></pre>
- </details>
- @endif
- @if(!$loop->last)
- <hr>
- @endif
+ @include('api-docs.parts.endpoint', ['endpoint' => $endpoint, 'loop' => $loop])
@endforeach
</section>
@endforeach
--- /dev/null
+<h6 class="text-uppercase text-muted float right">{{ $endpoint['controller_method_kebab'] }}</h6>
+
+<h5 id="{{ $endpoint['name'] }}" class="text-mono mb-m">
+ <span class="api-method" data-method="{{ $endpoint['method'] }}">{{ $endpoint['method'] }}</span>
+ @if($endpoint['controller_method_kebab'] === 'list')
+ <a style="color: inherit;" target="_blank" rel="noopener" href="{{ url($endpoint['uri']) }}">{{ url($endpoint['uri']) }}</a>
+ @else
+ {{ url($endpoint['uri']) }}
+ @endif
+</h5>
+
+<p class="mb-m">{{ $endpoint['description'] ?? '' }}</p>
+
+@if($endpoint['body_params'] ?? false)
+ <details class="mb-m">
+ <summary class="text-muted">Body Parameters</summary>
+ <table class="table">
+ <tr>
+ <th>Param Name</th>
+ <th>Value Rules</th>
+ </tr>
+ @foreach($endpoint['body_params'] as $paramName => $rules)
+ <tr>
+ <td>{{ $paramName }}</td>
+ <td>
+ @foreach($rules as $rule)
+ <code class="mr-xs">{{ $rule }}</code>
+ @endforeach
+ </td>
+ </tr>
+ @endforeach
+ </table>
+ </details>
+@endif
+
+@if($endpoint['example_request'] ?? false)
+ <details details-highlighter class="mb-m">
+ <summary class="text-muted">Example Request</summary>
+ <pre><code class="language-json">{{ $endpoint['example_request'] }}</code></pre>
+ </details>
+@endif
+
+@if($endpoint['example_response'] ?? false)
+ <details details-highlighter class="mb-m">
+ <summary class="text-muted">Example Response</summary>
+ <pre><code class="language-json">{{ $endpoint['example_response'] }}</code></pre>
+ </details>
+@endif
+
+@if(!$loop->last)
+ <hr>
+@endif
\ No newline at end of file
--- /dev/null
+<h1 class="list-heading text-capitals mb-l">Getting Started</h1>
+
+<h5 id="authentication" class="text-mono mb-m">Authentication</h5>
+<p>
+ To access the API a user has to have the <em>"Access System API"</em> permission enabled on one of their assigned roles.
+ Permissions to content accessed via the API is limited by the roles & permissions assigned to the user that's used to access the API.
+</p>
+<p>Authentication to use the API is primarily done using API Tokens. Once the <em>"Access System API"</em> permission has been assigned to a user, a "API Tokens" section should be visible when editing their user profile. Choose "Create Token" and enter an appropriate name and expiry date, relevant for your API usage then press "Save". A "Token ID" and "Token Secret" will be immediately displayed. These values should be used as a header in API HTTP requests in the following format:</p>
+<pre><code class="language-css">Authorization: Token <token_id>:<token_secret></code></pre>
+<p>Here's an example of an authorized cURL request to list books in the system:</p>
+<pre><code class="language-shell">curl --request GET \
+ --url https://example.com/api/books \
+ --header 'Authorization: Token C6mdvEQTGnebsmVn3sFNeeuelGEBjyQp:NOvD3VlzuSVuBPNaf1xWHmy7nIRlaj22'</code></pre>
+<p>If already logged into the system within the browser, via a user account with permission to access the API, the system will also accept an existing session meaning you can browse API endpoints directly in the browser or use the browser devtools to play with the API.</p>
+
+<hr>
+
+<h5 id="request-format" class="text-mono mb-m">Request Format</h5>
+<p>The API is primarily design to be interfaced using JSON so the majority of API endpoints, that accept data, will read JSON request data although <code>application/x-www-form-urlencoded</code> request data is also accepted. Endpoints that receive file data will need data sent in a <code>multipart/form-data</code> format although this will be highlighted in the documentation for such endpoints.</p>
+<p>For endpoints in this documentation that accept data, a "Body Parameters" table will be available showing the parameters that will accepted in the request. Any rules for the values of such parameters, such as the data-type or if they're required, will be shown alongside the parameter name.</p>
+
+<hr>
+
+<h5 id="listing-endpoints" class="text-mono mb-m">Listing Endpoints</h5>
+<p>Some endpoints will return a list of data models. These endpoints will return an array of the model data under a <code>data</code> property along with a numeric <code>total</code> property to indicate the total number of records found for the query within the system. Here's an example of a listing response:</p>
+<pre><code class="language-json">{
+ "data": [
+ {
+ "id": 1,
+ "name": "BookStack User Guide",
+ "slug": "bookstack-user-guide",
+ "description": "This is a general guide on using BookStack on a day-to-day basis.",
+ "created_at": "2019-05-05 21:48:46",
+ "updated_at": "2019-12-11 20:57:31",
+ "created_by": 1,
+ "updated_by": 1,
+ "image_id": 3
+ }
+ ],
+ "total": 16
+}</code></pre>
+<p>
+ There are a number of standard URL parameters that can be supplied to manipulate and page through the results returned from a listing endpoint:
+</p>
+<table class="table">
+ <tr>
+ <th>Parameter</th>
+ <th>Details</th>
+ <th width="30%">Examples</th>
+ </tr>
+ <tr>
+ <td>count</td>
+ <td>
+ Specify how many records will be returned in the response. <br>
+ (Default: {{ config('api.default_item_count') }}, Max: {{ config('api.max_item_count') }})
+ </td>
+ <td>Limit the count to 50<br><code>?count=50</code></td>
+ </tr>
+ <tr>
+ <td>offset</td>
+ <td>
+ Specify how many records to skip over in the response. <br>
+ (Default: 0)
+ </td>
+ <td>Skip over the first 100 records<br><code>?offset=100</code></td>
+ </tr>
+ <tr>
+ <td>sort</td>
+ <td>
+ Specify what field is used to sort the data and the direction of the sort (Ascending or Descending).<br>
+ Value is the name of a field, A <code>+</code> or <code>-</code> prefix dictates ordering. <br>
+ Direction defaults to ascending. <br>
+ Can use most fields shown in the response.
+ </td>
+ <td>
+ Sort by name ascending<br><code>?sort=+name</code> <br> <br>
+ Sort by "Created At" date descending<br><code>?sort=-created_at</code>
+ </td>
+ </tr>
+ <tr>
+ <td>filter[<field>]</td>
+ <td>
+ Specify a filter to be applied to the query. Can use most fields shown in the response. <br>
+ By default a filter will apply a "where equals" query but the below operations are available using the format filter[<field>:<operation>] <br>
+ <table>
+ <tr>
+ <td>eq</td>
+ <td>Where <code><field></code> equals the filter value.</td>
+ </tr>
+ <tr>
+ <td>ne</td>
+ <td>Where <code><field></code> does not equal the filter value.</td>
+ </tr>
+ <tr>
+ <td>gt</td>
+ <td>Where <code><field></code> is greater than the filter value.</td>
+ </tr>
+ <tr>
+ <td>lt</td>
+ <td>Where <code><field></code> is less than the filter value.</td>
+ </tr>
+ <tr>
+ <td>gte</td>
+ <td>Where <code><field></code> is greater than or equal to the filter value.</td>
+ </tr>
+ <tr>
+ <td>lte</td>
+ <td>Where <code><field></code> is less than or equal to the filter value.</td>
+ </tr>
+ <tr>
+ <td>like</td>
+ <td>
+ Where <code><field></code> is "like" the filter value. <br>
+ <code>%</code> symbols can be used as wildcards.
+ </td>
+ </tr>
+ </table>
+ </td>
+ <td>
+ Filter where id is 5: <br><code>?filter[id]=5</code><br><br>
+ Filter where id is not 5: <br><code>?filter[id:ne]=5</code><br><br>
+ Filter where name contains "cat": <br><code>?filter[name:like]=%cat%</code><br><br>
+ Filter where created after 2020-01-01: <br><code>?filter[created_at:gt]=2020-01-01</code>
+ </td>
+ </tr>
+</table>
+
+<hr>
+
+<h5 id="error-handling" class="text-mono mb-m">Error Handling</h5>
+<p>
+ Successful responses will return a 200 or 204 HTTP response code. Errors will return a 4xx or a 5xx HTTP response code depending on the type of error. Errors follow a standard format as shown below. The message provided may be translated depending on the configured language of the system in addition to the API users' language preference. The code provided in the JSON response will match the HTTP response code.
+</p>
+
+<pre><code class="language-json">{
+ "error": {
+ "code": 401,
+ "message": "No authorization token found on the request"
+ }
+}
+</code></pre>
\ No newline at end of file
<button refs="tabs@toggleLink" type="button" class="tab-item {{ $attachment->external ? 'selected' : '' }}">{{ trans('entities.attachments_set_link') }}</button>
</div>
<div refs="tabs@contentFile" class="mb-m {{ $attachment->external ? 'hidden' : '' }}">
- @include('components.dropzone', [
+ @include('form.dropzone', [
'placeholder' => trans('entities.attachments_edit_drop_upload'),
'url' => url('/attachments/upload/' . $attachment->id),
'successMessage' => trans('entities.attachments_file_updated'),
@include('attachments.manager-list', ['attachments' => $page->attachments->all()])
</div>
<div refs="tabs@contentUpload" class="hidden">
- @include('components.dropzone', [
+ @include('form.dropzone', [
'placeholder' => trans('entities.attachments_dropzone'),
'url' => url('/attachments/upload?uploaded_to=' . $page->id),
'successMessage' => trans('entities.attachments_file_uploaded'),
-@extends('simple-layout')
+@extends('layouts.simple')
@section('content')
-@extends('simple-layout')
+@extends('layouts.simple')
@section('content')
<div class="card content-wrap auto-height">
<h1 class="list-heading">{{ Str::title(trans('auth.log_in')) }}</h1>
- @include('auth.forms.login.' . $authMethod)
+ @include('auth.parts.login-form-' . $authMethod)
@if(count($socialDrivers) > 0)
<hr class="my-l">
<div class="grid half collapse-xs gap-xl v-center">
<div class="text-left ml-xxs">
- @include('components.custom-checkbox', [
+ @include('form.custom-checkbox', [
'name' => 'remember',
'checked' => false,
'value' => 'on',
-@extends('simple-layout')
+@extends('layouts.simple')
@section('content')
<div class="container very-small mt-xl">
-@extends('simple-layout')
+@extends('layouts.simple')
@section('content')
-@extends('simple-layout')
+@extends('layouts.simple')
@section('content')
-@extends('simple-layout')
+@extends('layouts.simple')
@section('content')
<div class="container very-small">
-@extends('simple-layout')
+@extends('layouts.simple')
@section('content')
{!! csrf_field() !!}
<div class="form-group">
<label for="email">{{ trans('auth.email') }}</label>
- @if(auth()->check())
- @include('form.text', ['name' => 'email', 'model' => auth()->user()])
+ @if($user)
+ @include('form.text', ['name' => 'email', 'model' => $user])
@else
@include('form.text', ['name' => 'email'])
@endif
+++ /dev/null
-<div class="breadcrumbs">
- <a href="{{$book->getUrl()}}" class="text-book text-button">@icon('book'){{ $book->getShortName() }}</a>
-</div>
\ No newline at end of file
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container small">
<div class="my-s">
@if (isset($bookshelf))
- @include('partials.breadcrumbs', ['crumbs' => [
+ @include('entities.breadcrumbs', ['crumbs' => [
$bookshelf,
$bookshelf->getUrl('/create-book') => [
'text' => trans('entities.books_create'),
]
]])
@else
- @include('partials.breadcrumbs', ['crumbs' => [
+ @include('entities.breadcrumbs', ['crumbs' => [
'/books' => [
'text' => trans('entities.books'),
'icon' => 'book'
<main class="content-wrap card">
<h1 class="list-heading">{{ trans('entities.books_create') }}</h1>
<form action="{{ isset($bookshelf) ? $bookshelf->getUrl('/create-book') : url('/books') }}" method="POST" enctype="multipart/form-data">
- @include('books.form', ['returnLocation' => isset($bookshelf) ? $bookshelf->getUrl() : url('/books')])
+ @include('books.parts.form', ['returnLocation' => isset($bookshelf) ? $bookshelf->getUrl() : url('/books')])
</form>
</main>
</div>
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container small">
<div class="my-s">
- @include('partials.breadcrumbs', ['crumbs' => [
+ @include('entities.breadcrumbs', ['crumbs' => [
$book,
$book->getUrl('/delete') => [
'text' => trans('entities.books_delete'),
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container small">
<div class="my-s">
- @include('partials.breadcrumbs', ['crumbs' => [
+ @include('entities.breadcrumbs', ['crumbs' => [
$book,
$book->getUrl('/edit') => [
'text' => trans('entities.books_edit'),
<h1 class="list-heading">{{ trans('entities.books_edit') }}</h1>
<form action="{{ $book->getUrl() }}" method="POST" enctype="multipart/form-data">
<input type="hidden" name="_method" value="PUT">
- @include('books.form', ['model' => $book, 'returnLocation' => $book->getUrl()])
+ @include('books.parts.form', ['model' => $book, 'returnLocation' => $book->getUrl()])
</form>
</main>
</div>
-@extends('export-layout')
+@extends('layouts.export')
@section('title', $book->name)
-@extends('tri-layout')
+@extends('layouts.tri')
@section('body')
- @include('books.list', ['books' => $books, 'view' => $view])
+ @include('books.parts.list', ['books' => $books, 'view' => $view])
@stop
@section('left')
@if($recents)
<div id="recents" class="mb-xl">
<h5>{{ trans('entities.recently_viewed') }}</h5>
- @include('partials.entity-list', ['entities' => $recents, 'style' => 'compact'])
+ @include('entities.list', ['entities' => $recents, 'style' => 'compact'])
</div>
@endif
<div id="popular" class="mb-xl">
<h5>{{ trans('entities.books_popular') }}</h5>
@if(count($popular) > 0)
- @include('partials.entity-list', ['entities' => $popular, 'style' => 'compact'])
+ @include('entities.list', ['entities' => $popular, 'style' => 'compact'])
@else
<div class="body text-muted">{{ trans('entities.books_popular_empty') }}</div>
@endif
<div id="new" class="mb-xl">
<h5>{{ trans('entities.books_new') }}</h5>
@if(count($popular) > 0)
- @include('partials.entity-list', ['entities' => $new, 'style' => 'compact'])
+ @include('entities.list', ['entities' => $new, 'style' => 'compact'])
@else
<div class="body text-muted">{{ trans('entities.books_new_empty') }}</div>
@endif
</a>
@endif
- @include('partials.view-toggle', ['view' => $view, 'type' => 'books'])
+ @include('entities.view-toggle', ['view' => $view, 'type' => 'books'])
</div>
</div>
<div class="collapse-content" collapsible-content>
<p class="small">{{ trans('common.cover_image_description') }}</p>
- @include('components.image-picker', [
+ @include('form.image-picker', [
'defaultImage' => url('/book_default_cover.png'),
'currentImage' => (isset($model) && $model->cover) ? $model->getBookCover() : url('/book_default_cover.png') ,
'name' => 'image',
<label for="tag-manager">{{ trans('entities.book_tags') }}</label>
</button>
<div class="collapse-content" collapsible-content>
- @include('components.tag-manager', ['entity' => $book ?? null])
+ @include('entities.tag-manager', ['entity' => $book ?? null])
</div>
</div>
<h1 class="list-heading">{{ trans('entities.books') }}</h1>
<div class="text-m-right my-m">
- @include('partials.sort', ['options' => [
+ @include('entities.sort', ['options' => [
'name' => trans('common.sort_name'),
'created_at' => trans('common.sort_created_at'),
'updated_at' => trans('common.sort_updated_at'),
@if($view === 'list')
<div class="entity-list">
@foreach($books as $book)
- @include('books.list-item', ['book' => $book])
+ @include('books.parts.list-item', ['book' => $book])
@endforeach
</div>
@else
<div class="grid third">
@foreach($books as $key => $book)
- @include('partials.entity-grid-item', ['entity' => $book])
+ @include('entities.grid-item', ['entity' => $book])
@endforeach
</div>
@endif
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container">
<div class="my-s">
- @include('partials.breadcrumbs', ['crumbs' => [
+ @include('entities.breadcrumbs', ['crumbs' => [
$book,
$book->getUrl('/permissions') => [
'text' => trans('entities.books_permissions'),
-@extends('tri-layout')
+@extends('layouts.tri')
@section('container-attrs')
component="entity-search"
@section('body')
<div class="mb-s">
- @include('partials.breadcrumbs', ['crumbs' => [
+ @include('entities.breadcrumbs', ['crumbs' => [
$book,
]])
</div>
<div class="entity-list book-contents">
@foreach($bookChildren as $childElement)
@if($childElement->isA('chapter'))
- @include('chapters.list-item', ['chapter' => $childElement])
+ @include('chapters.parts.list-item', ['chapter' => $childElement])
@else
- @include('pages.list-item', ['page' => $childElement])
+ @include('pages.parts.list-item', ['page' => $childElement])
@endif
@endforeach
</div>
@endif
</div>
- @include('partials.entity-search-results')
+ @include('entities.search-results')
</main>
@stop
<div class="mb-xl">
<h5>{{ trans('common.details') }}</h5>
<div class="text-small text-muted blended-links">
- @include('partials.entity-meta', ['entity' => $book])
+ @include('entities.meta', ['entity' => $book])
@if($book->restricted)
<div class="active-restriction">
@if(userCan('restrictions-manage', $book))
<hr class="primary-background">
@if(signedInUser())
- @include('partials.entity-favourite-action', ['entity' => $book])
+ @include('entities.favourite-action', ['entity' => $book])
+ @endif
+ @if(userCan('content-export'))
+ @include('entities.export-menu', ['entity' => $book])
@endif
- @include('partials.entity-export-menu', ['entity' => $book])
</div>
</div>
@section('left')
- @include('partials.entity-search-form', ['label' => trans('entities.books_search_this')])
+ @include('entities.search-form', ['label' => trans('entities.books_search_this')])
@if($book->tags->count() > 0)
<div class="mb-xl">
- @include('components.tag-list', ['entity' => $book])
+ @include('entities.tag-list', ['entity' => $book])
</div>
@endif
@if(count($bookParentShelves) > 0)
<div class="actions mb-xl">
<h5>{{ trans('entities.shelves_long') }}</h5>
- @include('partials.entity-list', ['entities' => $bookParentShelves, 'style' => 'compact'])
+ @include('entities.list', ['entities' => $bookParentShelves, 'style' => 'compact'])
</div>
@endif
@if(count($activity) > 0)
<div class="mb-xl">
<h5>{{ trans('entities.recent_activity') }}</h5>
- @include('partials.activity-list', ['activity' => $activity])
+ @include('common.activity-list', ['activity' => $activity])
</div>
@endif
@stop
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container">
<div class="my-s">
- @include('partials.breadcrumbs', ['crumbs' => [
+ @include('entities.breadcrumbs', ['crumbs' => [
$book,
$book->getUrl('/sort') => [
'text' => trans('entities.books_sort'),
<div book-sort class="card content-wrap">
<h1 class="list-heading mb-l">{{ trans('entities.books_sort') }}</h1>
<div book-sort-boxes>
- @include('books.sort-box', ['book' => $book, 'bookChildren' => $bookChildren])
+ @include('books.parts.sort-box', ['book' => $book, 'bookChildren' => $bookChildren])
</div>
<form action="{{ $book->getUrl('/sort') }}" method="POST">
<main class="card content-wrap">
<h2 class="list-heading mb-m">{{ trans('entities.books_sort_show_other') }}</h2>
- @include('components.entity-selector', ['name' => 'books_list', 'selectorSize' => 'compact', 'entityTypes' => 'book', 'entityPermission' => 'update', 'showAdd' => true])
+ @include('entities.selector', ['name' => 'books_list', 'selectorSize' => 'compact', 'entityTypes' => 'book', 'entityPermission' => 'update', 'showAdd' => true])
</main>
</div>
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container small">
<div class="my-s">
- @include('partials.breadcrumbs', ['crumbs' => [
+ @include('entities.breadcrumbs', ['crumbs' => [
$book,
$book->getUrl('create-chapter') => [
'text' => trans('entities.chapters_create'),
<main class="content-wrap card">
<h1 class="list-heading">{{ trans('entities.chapters_create') }}</h1>
<form action="{{ $book->getUrl('/create-chapter') }}" method="POST">
- @include('chapters.form')
+ @include('chapters.parts.form')
</form>
</main>
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container small">
<div class="my-s">
- @include('partials.breadcrumbs', ['crumbs' => [
+ @include('entities.breadcrumbs', ['crumbs' => [
$chapter->book,
$chapter,
$chapter->getUrl('/delete') => [
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container small">
<div class="my-s">
- @include('partials.breadcrumbs', ['crumbs' => [
+ @include('entities.breadcrumbs', ['crumbs' => [
$book,
$chapter,
$chapter->getUrl('/edit') => [
<h1 class="list-heading">{{ trans('entities.chapters_edit') }}</h1>
<form action="{{ $chapter->getUrl() }}" method="POST">
<input type="hidden" name="_method" value="PUT">
- @include('chapters.form', ['model' => $chapter])
+ @include('chapters.parts.form', ['model' => $chapter])
</form>
</main>
-@extends('export-layout')
+@extends('layouts.export')
@section('title', $chapter->name)
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container small">
<div class="my-s">
- @include('partials.breadcrumbs', ['crumbs' => [
+ @include('entities.breadcrumbs', ['crumbs' => [
$chapter->book,
$chapter,
$chapter->getUrl('/move') => [
{!! csrf_field() !!}
<input type="hidden" name="_method" value="PUT">
- @include('components.entity-selector', ['name' => 'entity_selection', 'selectorSize' => 'large', 'entityTypes' => 'book', 'entityPermission' => 'chapter-create'])
+ @include('entities.selector', ['name' => 'entity_selection', 'selectorSize' => 'large', 'entityTypes' => 'book', 'entityPermission' => 'chapter-create'])
<div class="form-group text-right">
<a href="{{ $chapter->getUrl() }}" class="button outline">{{ trans('common.cancel') }}</a>
<ul class="sub-menu inset-list @if($isOpen) open @endif" @if($isOpen) style="display: block;" @endif role="menu">
@foreach($bookChild->visible_pages as $childPage)
<li class="list-item-page {{ $childPage->isA('page') && $childPage->draft ? 'draft' : '' }}" role="presentation">
- @include('partials.entity-list-item-basic', ['entity' => $childPage, 'classes' => $current->matches($childPage)? 'selected' : '' ])
+ @include('entities.list-item-basic', ['entity' => $childPage, 'classes' => $current->matches($childPage)? 'selected' : '' ])
</li>
@endforeach
</ul>
<label for="tags">{{ trans('entities.chapter_tags') }}</label>
</button>
<div class="collapse-content" collapsible-content>
- @include('components.tag-manager', ['entity' => $chapter ?? null])
+ @include('entities.tag-manager', ['entity' => $chapter ?? null])
</div>
</div>
class="text-muted chapter-expansion-toggle">@icon('caret-right') <span>{{ trans_choice('entities.x_pages', $chapter->visible_pages->count()) }}</span></button>
<div class="inset-list">
<div class="entity-list-item-children">
- @include('partials.entity-list', ['entities' => $chapter->visible_pages])
+ @include('entities.list', ['entities' => $chapter->visible_pages])
</div>
</div>
</div>
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container">
<div class="my-s">
- @include('partials.breadcrumbs', ['crumbs' => [
+ @include('entities.breadcrumbs', ['crumbs' => [
$chapter->book,
$chapter,
$chapter->getUrl('/permissions') => [
-@extends('tri-layout')
+@extends('layouts.tri')
@section('container-attrs')
component="entity-search"
@section('body')
<div class="mb-m print-hidden">
- @include('partials.breadcrumbs', ['crumbs' => [
+ @include('entities.breadcrumbs', ['crumbs' => [
$chapter->book,
$chapter,
]])
@if(count($pages) > 0)
<div class="entity-list book-contents">
@foreach($pages as $page)
- @include('pages.list-item', ['page' => $page])
+ @include('pages.parts.list-item', ['page' => $page])
@endforeach
</div>
@else
@endif
</div>
- @include('partials.entity-search-results')
+ @include('entities.search-results')
</main>
- @include('partials.entity-sibling-navigation', ['next' => $next, 'previous' => $previous])
+ @include('entities.sibling-navigation', ['next' => $next, 'previous' => $previous])
@stop
<div class="mb-xl">
<h5>{{ trans('common.details') }}</h5>
<div class="blended-links text-small text-muted">
- @include('partials.entity-meta', ['entity' => $chapter])
+ @include('entities.meta', ['entity' => $chapter])
@if($book->restricted)
<div class="active-restriction">
<hr class="primary-background"/>
@if(signedInUser())
- @include('partials.entity-favourite-action', ['entity' => $chapter])
+ @include('entities.favourite-action', ['entity' => $chapter])
+ @endif
+ @if(userCan('content-export'))
+ @include('entities.export-menu', ['entity' => $chapter])
@endif
- @include('partials.entity-export-menu', ['entity' => $chapter])
</div>
</div>
@stop
@section('left')
- @include('partials.entity-search-form', ['label' => trans('entities.chapters_search_this')])
+ @include('entities.search-form', ['label' => trans('entities.chapters_search_this')])
@if($chapter->tags->count() > 0)
<div class="mb-xl">
- @include('components.tag-list', ['entity' => $chapter])
+ @include('entities.tag-list', ['entity' => $chapter])
</div>
@endif
- @include('partials.book-tree', ['book' => $book, 'sidebarTree' => $sidebarTree])
+ @include('entities.book-tree', ['book' => $book, 'sidebarTree' => $sidebarTree])
@stop
<div comment-content class="content px-s pb-s">
<div class="form-group loading" style="display: none;">
- @include('partials.loading-icon', ['text' => trans('entities.comment_deleting')])
+ @include('common.loading-icon', ['text' => trans('entities.comment_deleting')])
</div>
{!! $comment->html !!}
</div>
<button type="submit" class="button">{{ trans('entities.comment_save') }}</button>
</div>
<div class="form-group loading" style="display: none;">
- @include('partials.loading-icon', ['text' => trans('entities.comment_saving')])
+ @include('common.loading-icon', ['text' => trans('entities.comment_saving')])
</div>
</form>
</div>
<button type="submit" class="button">{{ trans('entities.comment_save') }}</button>
</div>
<div class="form-group loading" style="display: none;">
- @include('partials.loading-icon', ['text' => trans('entities.comment_saving')])
+ @include('common.loading-icon', ['text' => trans('entities.comment_saving')])
</div>
</form>
</div>
<div class="activity-list">
@foreach($activity as $activityItem)
<div class="activity-list-item">
- @include('partials.activity-item', ['activity' => $activityItem])
+ @include('common.activity-item', ['activity' => $activityItem])
</div>
@endforeach
</div>
+@inject('headContent', 'BookStack\Theming\CustomHtmlHeadContentProvider')
+
@if(setting('app-custom-head') && \Route::currentRouteName() !== 'settings')
<!-- Custom user content -->
-{!! setting('app-custom-head') !!}
+{!! $headContent->forWeb() !!}
<!-- End custom user content -->
@endif
\ No newline at end of file
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container small pt-xl">
<h1 class="list-heading">{{ $title }}</h1>
<div class="book-contents">
- @include('partials.entity-list', ['entities' => $entities, 'style' => 'detailed'])
+ @include('entities.list', ['entities' => $entities, 'style' => 'detailed'])
</div>
<div class="text-center">
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container small pt-xl">
<h1 class="list-heading">{{ $title }}</h1>
<div class="book-contents">
- @include('partials.entity-list', ['entities' => $entities, 'style' => 'detailed'])
+ @include('entities.list', ['entities' => $entities, 'style' => 'detailed'])
</div>
<div class="text-right">
--- /dev/null
+@inject('headContent', 'BookStack\Theming\CustomHtmlHeadContentProvider')
+
+@if(setting('app-custom-head'))
+<!-- Custom user content -->
+{!! $headContent->forExport() !!}
+<!-- End custom user content -->
+@endif
\ No newline at end of file
</li>
<li><hr></li>
<li>
- @include('partials.dark-mode-toggle')
+ @include('common.dark-mode-toggle')
</li>
</ul>
</div>
<ul class="sidebar-page-list mt-xs menu entity-list">
@if (userCan('view', $book))
<li class="list-item-book book">
- @include('partials.entity-list-item-basic', ['entity' => $book, 'classes' => ($current->matches($book)? 'selected' : '')])
+ @include('entities.list-item-basic', ['entity' => $book, 'classes' => ($current->matches($book)? 'selected' : '')])
</li>
@endif
@foreach($sidebarTree as $bookChild)
<li class="list-item-{{ $bookChild->getType() }} {{ $bookChild->getType() }} {{ $bookChild->isA('page') && $bookChild->draft ? 'draft' : '' }}">
- @include('partials.entity-list-item-basic', ['entity' => $bookChild, 'classes' => $current->matches($bookChild)? 'selected' : ''])
+ @include('entities.list-item-basic', ['entity' => $bookChild, 'classes' => $current->matches($bookChild)? 'selected' : ''])
@if($bookChild->isA('chapter') && count($bookChild->visible_pages) > 0)
<div class="entity-list-item no-hover">
<span role="presentation" class="icon text-chapter"></span>
<div class="content">
- @include('chapters.child-menu', [
+ @include('chapters.parts.child-menu', [
'chapter' => $bookChild,
'current' => $current,
'isOpen' => $bookChild->matchesOrContains($current)
type="text">
</div>
<div refs="dropdown-search@loading">
- @include('partials.loading-icon')
+ @include('common.loading-icon')
</div>
<div refs="dropdown-search@listContainer" class="dropdown-search-list px-m"></div>
</div>
</a>
@elseif($isEntity && userCan('view', $crumb))
@if($breadcrumbCount > 0)
- @include('partials.breadcrumb-listing', ['entity' => $crumb])
+ @include('entities.breadcrumb-listing', ['entity' => $crumb])
@endif
<a href="{{ $crumb->getUrl() }}" class="text-{{$crumb->getType()}} icon-list-item outline-hover">
<span>@icon($crumb->getType())</span>
<div class="entity-list {{ $style ?? '' }}">
@if(count($entities) > 0)
@foreach($entities as $index => $entity)
- @include('partials.entity-list-item-basic', ['entity' => $entity])
+ @include('entities.list-item-basic', ['entity' => $entity])
@endforeach
@else
<p class="text-muted empty-text">
-@component('partials.entity-list-item-basic', ['entity' => $entity])
+@component('entities.list-item-basic', ['entity' => $entity])
<div class="entity-item-snippet">
@if(($showTags ?? false) && $entity->tags->count() > 0)
<div class="entity-item-tags mt-xs">
- @include('components.tag-list', ['entity' => $entity, 'linked' => false ])
+ @include('entities.tag-list', ['entity' => $entity, 'linked' => false ])
</div>
@endif
@if(count($entities) > 0)
<div class="entity-list {{ $style ?? '' }}">
@foreach($entities as $index => $entity)
- @include('partials.entity-list-item', ['entity' => $entity, 'showPath' => $showPath ?? false, 'showTags' => $showTags ?? false])
+ @include('entities.list-item', ['entity' => $entity, 'showPath' => $showPath ?? false, 'showTags' => $showTags ?? false])
@endforeach
</div>
@else
</div>
<div refs="entity-search@loadingBlock">
- @include('partials.loading-icon')
+ @include('common.loading-icon')
</div>
<div class="book-contents" refs="entity-search@searchResults"></div>
</div>
\ No newline at end of file
<div class="popup-title">{{ trans('entities.entity_select') }}</div>
<button refs="popup@hide" type="button" class="popup-header-close">x</button>
</div>
- @include('components.entity-selector', ['name' => 'entity-selector'])
+ @include('entities.selector', ['name' => 'entity-selector'])
<div class="popup-footer">
<button refs="entity-selector-popup@select" type="button" disabled="true" class="button corner-button">{{ trans('common.select') }}</button>
</div>
option:entity-selector:entity-permission="{{ $entityPermission ?? 'view' }}">
<input refs="entity-selector@input" type="hidden" name="{{$name}}" value="">
<input type="text" placeholder="{{ trans('common.search') }}" @if($autofocus ?? false) autofocus @endif refs="entity-selector@search">
- <div class="text-center loading" refs="entity-selector@loading">@include('partials.loading-icon')</div>
+ <div class="text-center loading" refs="entity-selector@loading">@include('common.loading-icon')</div>
<div refs="entity-selector@results"></div>
@if($showAdd ?? false)
<div class="entity-selector-add">
<div component="sortable-list"
option:sortable-list:handle-selector=".handle">
- @include('components.tag-manager-list', ['tags' => $entity ? $entity->tags->all() : []])
+ @include('entities.tag-manager-list', ['tags' => $entity ? $entity->tags->all() : []])
</div>
<button refs="add-remove-rows@add" type="button" class="text-button">{{ trans('entities.tags_add') }}</button>
-@extends('simple-layout')
+@extends('layouts.simple')
@section('content')
<div class="container mt-l">
<div class="card mb-xl">
<h3 class="card-title">{{ trans('entities.pages_popular') }}</h3>
<div class="px-m">
- @include('partials.entity-list', ['entities' => (new \BookStack\Entities\Queries\Popular)->run(10, 0, ['page']), 'style' => 'compact'])
+ @include('entities.list', ['entities' => (new \BookStack\Entities\Queries\Popular)->run(10, 0, ['page']), 'style' => 'compact'])
</div>
</div>
</div>
<div class="card mb-xl">
<h3 class="card-title">{{ trans('entities.books_popular') }}</h3>
<div class="px-m">
- @include('partials.entity-list', ['entities' => (new \BookStack\Entities\Queries\Popular)->run(10, 0, ['book']), 'style' => 'compact'])
+ @include('entities.list', ['entities' => (new \BookStack\Entities\Queries\Popular)->run(10, 0, ['book']), 'style' => 'compact'])
</div>
</div>
</div>
<div class="card mb-xl">
<h3 class="card-title">{{ trans('entities.chapters_popular') }}</h3>
<div class="px-m">
- @include('partials.entity-list', ['entities' => (new \BookStack\Entities\Queries\Popular)->run(10, 0, ['chapter']), 'style' => 'compact'])
+ @include('entities.list', ['entities' => (new \BookStack\Entities\Queries\Popular)->run(10, 0, ['chapter']), 'style' => 'compact'])
</div>
</div>
</div>
-@extends('base')
+@extends('layouts.base')
@section('content')
-@extends('simple-layout')
+@extends('layouts.simple')
@section('content')
$errors?
$model?
--}}
-@include('components.custom-checkbox', [
+@include('form.custom-checkbox', [
'name' => $name,
'label' => $label,
'value' => 'true',
<div>
<div class="form-group">
<label for="owner">{{ trans('entities.permissions_owner') }}</label>
- @include('components.user-select', ['user' => $model->ownedBy, 'name' => 'owned_by', 'compact' => false])
+ @include('form.user-select', ['user' => $model->ownedBy, 'name' => 'owned_by', 'compact' => false])
</div>
</div>
</div>
+ @if($model instanceof \BookStack\Entities\Models\Bookshelf)
+ <p class="text-warn">{{ trans('entities.shelves_permissions_cascade_warning') }}</p>
+ @endif
+
<hr>
<table permissions-table class="table permissions-table toggle-switch-list" style="{{ !$model->restricted ? 'display: none' : '' }}">
$action
$model?
--}}
-@include('components.custom-checkbox', [
+@include('form.custom-checkbox', [
'name' => $name . '[' . $role->id . '][' . $action . ']',
'label' => $label,
'value' => 'true',
<div class="toggle-switch-list dual-column-content">
@foreach($roles as $role)
<div>
- @include('components.custom-checkbox', [
+ @include('form.custom-checkbox', [
'name' => $name . '[' . strval($role->id) . ']',
'label' => $role->display_name,
'value' => $role->id,
type="text">
</div>
<div refs="dropdown-search@loading" class="text-center">
- @include('partials.loading-icon')
+ @include('common.loading-icon')
</div>
<div refs="dropdown-search@listContainer" class="dropdown-search-list"></div>
</div>
-@extends('tri-layout')
+@extends('layouts.tri')
@section('body')
- @include('books.list', ['books' => $books, 'view' => $view])
+ @include('books.parts.list', ['books' => $books, 'view' => $view])
@stop
@section('left')
- @include('common.home-sidebar')
+ @include('home.parts.sidebar')
@stop
@section('right')
<span>{{ trans('entities.books_create') }}</span>
</a>
@endif
- @include('partials.view-toggle', ['view' => $view, 'type' => 'books'])
- @include('components.expand-toggle', ['classes' => 'text-primary', 'target' => '.entity-list.compact .entity-item-snippet', 'key' => 'home-details'])
- @include('partials.dark-mode-toggle', ['classes' => 'icon-list-item text-primary'])
+ @include('entities.view-toggle', ['view' => $view, 'type' => 'books'])
+ @include('home.parts.expand-toggle', ['classes' => 'text-primary', 'target' => '.entity-list.compact .entity-item-snippet', 'key' => 'home-details'])
+ @include('common.dark-mode-toggle', ['classes' => 'icon-list-item text-primary'])
</div>
</div>
@stop
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="grid half">
<div>
<div class="icon-list inline block">
- @include('components.expand-toggle', ['classes' => 'text-muted text-primary', 'target' => '.entity-list.compact .entity-item-snippet', 'key' => 'home-details'])
+ @include('home.parts.expand-toggle', ['classes' => 'text-muted text-primary', 'target' => '.entity-list.compact .entity-item-snippet', 'key' => 'home-details'])
</div>
</div>
<div class="text-m-right">
<div class="icon-list inline block">
- @include('partials.dark-mode-toggle', ['classes' => 'text-muted icon-list-item text-primary'])
+ @include('common.dark-mode-toggle', ['classes' => 'text-muted icon-list-item text-primary'])
</div>
</div>
</div>
<div id="recent-drafts" class="card mb-xl">
<h3 class="card-title">{{ trans('entities.my_recent_drafts') }}</h3>
<div class="px-m">
- @include('partials.entity-list', ['entities' => $draftPages, 'style' => 'compact'])
+ @include('entities.list', ['entities' => $draftPages, 'style' => 'compact'])
</div>
</div>
@endif
<div id="{{ auth()->check() ? 'recently-viewed' : 'recent-books' }}" class="card mb-xl">
<h3 class="card-title">{{ trans('entities.' . (auth()->check() ? 'my_recently_viewed' : 'books_recent')) }}</h3>
<div class="px-m">
- @include('partials.entity-list', [
+ @include('entities.list', [
'entities' => $recents,
'style' => 'compact',
'emptyText' => auth()->check() ? trans('entities.no_pages_viewed') : trans('entities.books_empty')
<a href="{{ url('/favourites') }}" class="no-color">{{ trans('entities.my_most_viewed_favourites') }}</a>
</h3>
<div class="px-m">
- @include('partials.entity-list', [
+ @include('entities.list', [
'entities' => $favourites,
'style' => 'compact',
])
<div id="recent-pages" class="card mb-xl">
<h3 class="card-title"><a class="no-color" href="{{ url("/pages/recently-updated") }}">{{ trans('entities.recently_updated_pages') }}</a></h3>
<div id="recently-updated-pages" class="px-m">
- @include('partials.entity-list', [
+ @include('entities.list', [
'entities' => $recentlyUpdatedPages,
'style' => 'compact',
'emptyText' => trans('entities.no_pages_recently_updated')
<div id="recent-activity">
<div class="card mb-xl">
<h3 class="card-title">{{ trans('entities.recent_activity') }}</h3>
- @include('partials.activity-list', ['activity' => $activity])
+ @include('common.activity-list', ['activity' => $activity])
</div>
</div>
</div>
@if(count($draftPages) > 0)
<div id="recent-drafts" class="mb-xl">
<h5>{{ trans('entities.my_recent_drafts') }}</h5>
- @include('partials.entity-list', ['entities' => $draftPages, 'style' => 'compact'])
+ @include('entities.list', ['entities' => $draftPages, 'style' => 'compact'])
</div>
@endif
<h5>
<a href="{{ url('/favourites') }}" class="no-color">{{ trans('entities.my_most_viewed_favourites') }}</a>
</h5>
- @include('partials.entity-list', [
+ @include('entities.list', [
'entities' => $favourites,
'style' => 'compact',
])
<div class="mb-xl">
<h5>{{ trans('entities.' . (auth()->check() ? 'my_recently_viewed' : 'books_recent')) }}</h5>
- @include('partials.entity-list', [
+ @include('entities.list', [
'entities' => $recents,
'style' => 'compact',
'emptyText' => auth()->check() ? trans('entities.no_pages_viewed') : trans('entities.books_empty')
<div class="mb-xl">
<h5><a class="no-color" href="{{ url("/pages/recently-updated") }}">{{ trans('entities.recently_updated_pages') }}</a></h5>
<div id="recently-updated-pages">
- @include('partials.entity-list', [
+ @include('entities.list', [
'entities' => $recentlyUpdatedPages,
'style' => 'compact',
'emptyText' => trans('entities.no_pages_recently_updated')
<div id="recent-activity" class="mb-xl">
<h5>{{ trans('entities.recent_activity') }}</h5>
- @include('partials.activity-list', ['activity' => $activity])
+ @include('common.activity-list', ['activity' => $activity])
</div>
\ No newline at end of file
-@extends('tri-layout')
+@extends('layouts.tri')
@section('body')
- @include('shelves.list', ['shelves' => $shelves, 'view' => $view])
+ @include('shelves.parts.list', ['shelves' => $shelves, 'view' => $view])
@stop
@section('left')
- @include('common.home-sidebar')
+ @include('home.parts.sidebar')
@stop
@section('right')
<span>{{ trans('entities.shelves_new_action') }}</span>
</a>
@endif
- @include('partials.view-toggle', ['view' => $view, 'type' => 'shelves'])
- @include('components.expand-toggle', ['classes' => 'text-primary', 'target' => '.entity-list.compact .entity-item-snippet', 'key' => 'home-details'])
- @include('partials.dark-mode-toggle', ['classes' => 'icon-list-item text-primary'])
+ @include('entities.view-toggle', ['view' => $view, 'type' => 'shelves'])
+ @include('home.parts.expand-toggle', ['classes' => 'text-primary', 'target' => '.entity-list.compact .entity-item-snippet', 'key' => 'home-details'])
+ @include('common.dark-mode-toggle', ['classes' => 'icon-list-item text-primary'])
</div>
</div>
@stop
-@extends('tri-layout')
+@extends('layouts.tri')
@section('body')
<div class="mt-m">
<main class="content-wrap card">
<div class="page-content" page-display="{{ $customHomepage->id }}">
- @include('pages.page-display', ['page' => $customHomepage])
+ @include('pages.parts.page-display', ['page' => $customHomepage])
</div>
</main>
</div>
@stop
@section('left')
- @include('common.home-sidebar')
+ @include('home.parts.sidebar')
@stop
@section('right')
<div class="actions mb-xl">
<h5>{{ trans('common.actions') }}</h5>
<div class="icon-list text-primary">
- @include('components.expand-toggle', ['classes' => 'text-primary', 'target' => '.entity-list.compact .entity-item-snippet', 'key' => 'home-details'])
- @include('partials.dark-mode-toggle', ['classes' => 'icon-list-item text-primary'])
+ @include('home.parts.expand-toggle', ['classes' => 'text-primary', 'target' => '.entity-list.compact .entity-item-snippet', 'key' => 'home-details'])
+ @include('common.dark-mode-toggle', ['classes' => 'icon-list-item text-primary'])
</div>
</div>
@stop
\ No newline at end of file
<meta property="og:title" content="{{ isset($pageTitle) ? $pageTitle . ' | ' : '' }}{{ setting('app-name') }}">
<meta property="og:url" content="{{ url()->current() }}">
@stack('social-meta')
-
<!-- Styles and Fonts -->
<link rel="stylesheet" href="{{ versioned_asset('dist/styles.css') }}">
@yield('head')
<!-- Custom Styles & Head Content -->
- @include('partials.custom-styles')
- @include('partials.custom-head')
+ @include('common.custom-styles')
+ @include('common.custom-head')
@stack('head')
</head>
<body class="@yield('body-class')">
- @include('common.parts.skip-to-content')
- @include('partials.notifications')
+ @include('common.skip-to-content')
+ @include('common.notifications')
@include('common.header')
<div id="content" components="@yield('content-components')" class="block">
</div>
@yield('bottom')
- <script src="{{ versioned_asset('dist/app.js') }}"></script>
+ <script src="{{ versioned_asset('dist/app.js') }}" nonce="{{ $cspNonce }}"></script>
@yield('scripts')
</body>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>@yield('title')</title>
- @include('partials.export-styles', ['format' => $format])
- @include('partials.export-custom-head')
+ @include('common.export-styles', ['format' => $format])
+ @include('common.export-custom-head')
</head>
<body>
<div class="page-content">
-@extends('base')
+@extends('layouts.base')
@section('content')
-@extends('base')
+@extends('layouts.base')
@section('body-class', 'tri-layout')
@section('content-components', 'tri-layout')
--- /dev/null
+@extends('layouts.simple')
+
+@section('body')
+
+ <div class="container very-small py-xl">
+ <div class="card content-wrap auto-height">
+ <h1 class="list-heading">{{ trans('auth.mfa_gen_backup_codes_title') }}</h1>
+ <p>{{ trans('auth.mfa_gen_backup_codes_desc') }}</p>
+
+ <div class="text-center mb-xs">
+ <div class="text-bigger code-base p-m" style="column-count: 2">
+ @foreach($codes as $code)
+ {{ $code }} <br>
+ @endforeach
+ </div>
+ </div>
+
+ <p class="text-right">
+ <a href="{{ $downloadUrl }}" download="backup-codes.txt" class="button outline small">{{ trans('auth.mfa_gen_backup_codes_download') }}</a>
+ </p>
+
+ <p class="callout warning">
+ {{ trans('auth.mfa_gen_backup_codes_usage_warning') }}
+ </p>
+
+ <form action="{{ url('/mfa/backup_codes/confirm') }}" method="POST">
+ {{ csrf_field() }}
+ <div class="mt-s text-right">
+ <a href="{{ url('/mfa/setup') }}" class="button outline">{{ trans('common.cancel') }}</a>
+ <button class="button">{{ trans('auth.mfa_gen_confirm_and_enable') }}</button>
+ </div>
+ </form>
+ </div>
+ </div>
+
+@stop
--- /dev/null
+<div class="grid half gap-xl">
+ <div>
+ <div class="setting-list-label">{{ trans('auth.mfa_option_' . $method . '_title') }}</div>
+ <p class="small">
+ {{ trans('auth.mfa_option_' . $method . '_desc') }}
+ </p>
+ </div>
+ <div class="pt-m">
+ @if($userMethods->has($method))
+ <div class="text-pos">
+ @icon('check-circle')
+ {{ trans('auth.mfa_setup_configured') }}
+ </div>
+ <a href="{{ url('/mfa/' . $method . '/generate') }}" class="button outline small">{{ trans('auth.mfa_setup_reconfigure') }}</a>
+ <div component="dropdown" class="inline relative">
+ <button type="button" refs="dropdown@toggle" class="button outline small">{{ trans('common.remove') }}</button>
+ <div refs="dropdown@menu" class="dropdown-menu">
+ <p class="text-neg small px-m mb-xs">{{ trans('auth.mfa_setup_remove_confirmation') }}</p>
+ <form action="{{ url('/mfa/' . $method . '/remove') }}" method="post">
+ {{ csrf_field() }}
+ {{ method_field('delete') }}
+ <button class="text-primary small delete">{{ trans('common.confirm') }}</button>
+ </form>
+ </div>
+ </div>
+ @else
+ <a href="{{ url('/mfa/' . $method . '/generate') }}" class="button outline">{{ trans('auth.mfa_setup_action') }}</a>
+ @endif
+ </div>
+</div>
\ No newline at end of file
--- /dev/null
+<div class="setting-list-label">{{ trans('auth.mfa_verify_backup_code') }}</div>
+
+<p class="small mb-m">{{ trans('auth.mfa_verify_backup_code_desc') }}</p>
+
+<form action="{{ url('/mfa/backup_codes/verify') }}" method="post">
+ {{ csrf_field() }}
+ <input type="text"
+ name="code"
+ placeholder="{{ trans('auth.mfa_verify_backup_code_enter_here') }}"
+ class="input-fill-width {{ $errors->has('code') ? 'neg' : '' }}">
+ @if($errors->has('code'))
+ <div class="text-neg text-small px-xs">{{ $errors->first('code') }}</div>
+ @endif
+ <div class="mt-s text-right">
+ <button class="button">{{ trans('common.confirm') }}</button>
+ </div>
+</form>
\ No newline at end of file
--- /dev/null
+<div class="setting-list-label">{{ trans('auth.mfa_option_totp_title') }}</div>
+
+<p class="small mb-m">{{ trans('auth.mfa_verify_totp_desc') }}</p>
+
+<form action="{{ url('/mfa/totp/verify') }}" method="post">
+ {{ csrf_field() }}
+ <input type="text"
+ name="code"
+ placeholder="{{ trans('auth.mfa_gen_totp_provide_code_here') }}"
+ class="input-fill-width {{ $errors->has('code') ? 'neg' : '' }}">
+ @if($errors->has('code'))
+ <div class="text-neg text-small px-xs">{{ $errors->first('code') }}</div>
+ @endif
+ <div class="mt-s text-right">
+ <button class="button">{{ trans('common.confirm') }}</button>
+ </div>
+</form>
\ No newline at end of file
--- /dev/null
+@extends('layouts.simple')
+
+@section('body')
+ <div class="container small py-xl">
+
+ <div class="card content-wrap auto-height">
+ <h1 class="list-heading">{{ trans('auth.mfa_setup') }}</h1>
+ <p class="mb-none"> {{ trans('auth.mfa_setup_desc') }}</p>
+
+ <div class="setting-list">
+ @foreach(['totp', 'backup_codes'] as $method)
+ @include('mfa.parts.setup-method-row', ['method' => $method])
+ @endforeach
+ </div>
+
+ </div>
+ </div>
+@stop
--- /dev/null
+@extends('layouts.simple')
+
+@section('body')
+
+ <div class="container very-small py-xl">
+ <div class="card content-wrap auto-height">
+ <h1 class="list-heading">{{ trans('auth.mfa_gen_totp_title') }}</h1>
+ <p>{{ trans('auth.mfa_gen_totp_desc') }}</p>
+ <p>{{ trans('auth.mfa_gen_totp_scan') }}</p>
+
+ <div class="text-center">
+ <div class="block inline">
+ {!! $svg !!}
+ </div>
+ <div class="code-base small text-muted px-s py-xs my-xs" style="overflow-x: scroll; white-space: nowrap;">
+ {{ $url }}
+ </div>
+ </div>
+
+ <h2 class="list-heading">{{ trans('auth.mfa_gen_totp_verify_setup') }}</h2>
+ <p id="totp-verify-input-details" class="mb-s">{{ trans('auth.mfa_gen_totp_verify_setup_desc') }}</p>
+ <form action="{{ url('/mfa/totp/confirm') }}" method="POST">
+ {{ csrf_field() }}
+ <input type="text"
+ name="code"
+ aria-labelledby="totp-verify-input-details"
+ placeholder="{{ trans('auth.mfa_gen_totp_provide_code_here') }}"
+ class="input-fill-width {{ $errors->has('code') ? 'neg' : '' }}">
+ @if($errors->has('code'))
+ <div class="text-neg text-small px-xs">{{ $errors->first('code') }}</div>
+ @endif
+ <div class="mt-s text-right">
+ <a href="{{ url('/mfa/setup') }}" class="button outline">{{ trans('common.cancel') }}</a>
+ <button class="button">{{ trans('auth.mfa_gen_confirm_and_enable') }}</button>
+ </div>
+ </form>
+ </div>
+ </div>
+
+@stop
--- /dev/null
+@extends('layouts.simple')
+
+@section('body')
+ <div class="container very-small py-xl">
+
+ <div class="card content-wrap auto-height">
+ <h1 class="list-heading">{{ trans('auth.mfa_verify_access') }}</h1>
+ <p class="mb-none">{{ trans('auth.mfa_verify_access_desc') }}</p>
+
+ @if(!$method)
+ <hr class="my-l">
+ <h5>{{ trans('auth.mfa_verify_no_methods') }}</h5>
+ <p class="small">{{ trans('auth.mfa_verify_no_methods_desc') }}</p>
+ <div>
+ <a href="{{ url('/mfa/setup') }}" class="button outline">{{ trans('common.configure') }}</a>
+ </div>
+ @endif
+
+ @if($method)
+ <hr class="my-l">
+ @include('mfa.parts.verify-' . $method)
+ @endif
+
+ @if(count($otherMethods) > 0)
+ <hr class="my-l">
+ @foreach($otherMethods as $otherMethod)
+ <div class="text-center">
+ <a href="{{ url("/mfa/verify?method={$otherMethod}") }}">{{ trans('auth.mfa_verify_use_' . $otherMethod) }}</a>
+ </div>
+ @endforeach
+ @endif
+
+ </div>
+ </div>
+@stop
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container small">
<div class="my-s">
- @include('partials.breadcrumbs', ['crumbs' => [
+ @include('entities.breadcrumbs', ['crumbs' => [
$page->book,
$page->chapter,
$page,
<label for="entity_selection">{{ trans('entities.pages_copy_desination') }}</label>
</button>
<div class="collapse-content" collapsible-content>
- @include('components.entity-selector', ['name' => 'entity_selection', 'selectorSize' => 'large', 'entityTypes' => 'book,chapter', 'entityPermission' => 'page-create'])
+ @include('entities.selector', ['name' => 'entity_selection', 'selectorSize' => 'large', 'entityTypes' => 'book,chapter', 'entityPermission' => 'page-create'])
</div>
</div>
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container small">
<div class="my-s">
- @include('partials.breadcrumbs', ['crumbs' => [
+ @include('entities.breadcrumbs', ['crumbs' => [
$page->book,
$page->chapter,
$page,
-@extends('base')
+@extends('layouts.base')
@section('head')
- <script src="{{ url('/libs/tinymce/tinymce.min.js?ver=4.9.4') }}"></script>
+ <script src="{{ url('/libs/tinymce/tinymce.min.js?ver=4.9.4') }}" nonce="{{ $cspNonce }}"></script>
@stop
@section('body-class', 'flexbox')
@if(!isset($isDraft))
<input type="hidden" name="_method" value="PUT">
@endif
- @include('pages.form', ['model' => $page])
- @include('pages.editor-toolbox')
+ @include('pages.parts.form', ['model' => $page])
+ @include('pages.parts.editor-toolbox')
</form>
</div>
- @include('components.image-manager', ['uploaded_to' => $page->id])
- @include('components.code-editor')
- @include('components.entity-selector-popup')
+ @include('pages.parts.image-manager', ['uploaded_to' => $page->id])
+ @include('pages.parts.code-editor')
+ @include('entities.selector-popup')
@stop
\ No newline at end of file
-@extends('export-layout')
+@extends('layouts.export')
@section('title', $page->name)
@section('content')
- @include('pages.page-display')
+ @include('pages.parts.page-display')
<hr>
<div class="text-muted text-small">
- @include('partials.entity-export-meta', ['entity' => $page])
+ @include('entities.export-meta', ['entity' => $page])
</div>
@endsection
\ No newline at end of file
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container small">
<div class="my-s">
- @include('partials.breadcrumbs', ['crumbs' => [
+ @include('entities.breadcrumbs', ['crumbs' => [
($parent->isA('chapter') ? $parent->book : null),
$parent,
$parent->getUrl('/create-page') => [
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container small">
<div class="my-s">
- @include('partials.breadcrumbs', ['crumbs' => [
+ @include('entities.breadcrumbs', ['crumbs' => [
$page->book,
$page->chapter,
$page,
{!! csrf_field() !!}
<input type="hidden" name="_method" value="PUT">
- @include('components.entity-selector', ['name' => 'entity_selection', 'selectorSize' => 'large', 'entityTypes' => 'book,chapter', 'entityPermission' => 'page-create', 'autofocus' => true])
+ @include('entities.selector', ['name' => 'entity_selection', 'selectorSize' => 'large', 'entityTypes' => 'book,chapter', 'entityPermission' => 'page-create', 'autofocus' => true])
<div class="form-group text-right">
<a href="{{ $page->getUrl() }}" class="button outline">{{ trans('common.cancel') }}</a>
<div toolbox-tab-content="tags">
<h4>{{ trans('entities.page_tags') }}</h4>
<div class="px-l">
- @include('components.tag-manager', ['entity' => $page])
+ @include('entities.tag-manager', ['entity' => $page])
</div>
</div>
<h4>{{ trans('entities.templates') }}</h4>
<div class="px-l">
- @include('pages.template-manager', ['page' => $page, 'templates' => $templates])
+ @include('pages.parts.template-manager', ['page' => $page, 'templates' => $templates])
</div>
</div>
{{--WYSIWYG Editor--}}
@if(setting('app-editor') === 'wysiwyg')
- @include('pages.wysiwyg-editor', ['model' => $model])
+ @include('pages.parts.wysiwyg-editor', ['model' => $model])
@endif
{{--Markdown Editor--}}
@if(setting('app-editor') === 'markdown')
- @include('pages.markdown-editor', ['model' => $model])
+ @include('pages.parts.markdown-editor', ['model' => $model])
@endif
</div>
<div class="image-manager-sidebar flex-container-column">
<div refs="image-manager@dropzoneContainer">
- @include('components.dropzone', [
+ @include('form.dropzone', [
'placeholder' => trans('components.image_dropzone'),
'successMessage' => trans('components.image_upload_success'),
'url' => url('/images/gallery?' . http_build_query(['uploaded_to' => $uploaded_to ?? 0]))
-@component('partials.entity-list-item-basic', ['entity' => $page])
+@component('entities.list-item-basic', ['entity' => $page])
<div class="entity-item-snippet">
<p class="text-muted break-text">{{ $page->getExcerpt() }}</p>
</div>
<p class="text-muted small mb-none">
{{ trans('entities.templates_explain_set_as_template') }}
</p>
- @include('components.toggle-switch', [
+ @include('form.toggle-switch', [
'name' => 'template',
'value' => old('template', $page->template ? 'true' : 'false') === 'true',
'label' => trans('entities.templates_set_as_template')
@endif
<div template-manager-list>
- @include('pages.template-manager-list', ['templates' => $templates])
+ @include('pages.parts.template-manager-list', ['templates' => $templates])
</div>
</div>
\ No newline at end of file
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container">
<div class="my-s">
- @include('partials.breadcrumbs', ['crumbs' => [
+ @include('entities.breadcrumbs', ['crumbs' => [
$page->book,
$page->chapter,
$page,
-@extends('tri-layout')
+@extends('layouts.tri')
@section('left')
<div id="revision-details" class="entity-details mb-xl">
<h5>{{ trans('common.details') }}</h5>
<div class="body text-small text-muted">
- @include('partials.entity-meta', ['entity' => $revision])
+ @include('entities.meta', ['entity' => $revision])
</div>
</div>
@stop
@section('body')
<div class="mb-m print-hidden">
- @include('partials.breadcrumbs', ['crumbs' => [
+ @include('entities.breadcrumbs', ['crumbs' => [
$page->$book,
$page->chapter,
$page,
<main class="card content-wrap">
<div class="page-content page-revision">
- @include('pages.page-display')
+ @include('pages.parts.page-display')
</div>
</main>
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container">
<div class="my-s">
- @include('partials.breadcrumbs', ['crumbs' => [
+ @include('entities.breadcrumbs', ['crumbs' => [
$page->book,
$page->chapter,
$page,
-@extends('tri-layout')
+@extends('layouts.tri')
@push('social-meta')
<meta property="og:description" content="{{ Str::limit($page->text, 100, '...') }}">
@section('body')
<div class="mb-m print-hidden">
- @include('partials.breadcrumbs', ['crumbs' => [
+ @include('entities.breadcrumbs', ['crumbs' => [
$page->book,
$page->hasChapter() ? $page->chapter : null,
$page,
<main class="content-wrap card">
<div class="page-content clearfix" page-display="{{ $page->id }}">
- @include('pages.pointer', ['page' => $page])
- @include('pages.page-display')
+ @include('pages.parts.pointer', ['page' => $page])
+ @include('pages.parts.page-display')
</div>
</main>
- @include('partials.entity-sibling-navigation', ['next' => $next, 'previous' => $previous])
+ @include('entities.sibling-navigation', ['next' => $next, 'previous' => $previous])
@if ($commentsEnabled)
@if(($previous || $next))
@if($page->tags->count() > 0)
<section>
- @include('components.tag-list', ['entity' => $page])
+ @include('entities.tag-list', ['entity' => $page])
</section>
@endif
</nav>
@endif
- @include('partials.book-tree', ['book' => $book, 'sidebarTree' => $sidebarTree])
+ @include('entities.book-tree', ['book' => $book, 'sidebarTree' => $sidebarTree])
@stop
@section('right')
<div id="page-details" class="entity-details mb-xl">
<h5>{{ trans('common.details') }}</h5>
<div class="body text-small blended-links">
- @include('partials.entity-meta', ['entity' => $page])
+ @include('entities.meta', ['entity' => $page])
@if($book->restricted)
<div class="active-restriction">
<hr class="primary-background"/>
@if(signedInUser())
- @include('partials.entity-favourite-action', ['entity' => $page])
+ @include('entities.favourite-action', ['entity' => $page])
+ @endif
+ @if(userCan('content-export'))
+ @include('entities.export-menu', ['entity' => $page])
@endif
- @include('partials.entity-export-menu', ['entity' => $page])
</div>
</div>
+++ /dev/null
-@if(setting('app-custom-head'))
-<!-- Custom user content -->
-{!! \BookStack\Util\HtmlContentFilter::removeScripts(setting('app-custom-head')) !!}
-<!-- End custom user content -->
-@endif
\ No newline at end of file
--- /dev/null
+# BookStack Views
+
+All views within this folder are [Laravel blade](https://laravel.com/docs/6.x/blade) views.
+
+### Overriding
+
+Views can be overridden on a per-file basis via the visual theme system.
+More information on this can be found within the `dev/docs/visual-theme-system.md`
+file within this project.
+
+### Convention
+
+Views are broken down into rough domain areas. These aren't too strict although many of the folders
+here will often match up to a HTTP controller.
+
+Within each folder views will be structured like so:
+
+```txt
+- folder/
+ - page-a.blade.php
+ - page-b.blade.php
+ - parts/
+ - partial-a.blade.php
+ - partial-b.blade.php
+ - subdomain/
+ - subdomain-page-a.blade.php
+ - subdomain-page-b.blade.php
+ - parts/
+ - subdomain-partial-a.blade.php
+ - subdomain-partial-b.blade.php
+```
+
+If a folder contains no pages at all (For example: `attachments`, `form`) and only partials, then
+the partials can be within the top-level folder instead of pages to prevent unneeded nesting.
+
+If a partial depends on another partial within the same directory, the naming of the child partials should be an extension of the parent.
+For example:
+
+```txt
+- tag-manager.blade.php
+- tag-manager-list.blade.php
+- tag-manager-input.blade.php
+```
\ No newline at end of file
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container mt-xl" id="search-system">
$types = explode('|', $options->filters['type'] ?? '');
$hasTypes = $types[0] !== '';
?>
- @include('search.form.type-filter', ['checked' => !$hasTypes || in_array('page', $types), 'entity' => 'page', 'transKey' => 'page'])
- @include('search.form.type-filter', ['checked' => !$hasTypes || in_array('chapter', $types), 'entity' => 'chapter', 'transKey' => 'chapter'])
+ @include('search.parts.type-filter', ['checked' => !$hasTypes || in_array('page', $types), 'entity' => 'page', 'transKey' => 'page'])
+ @include('search.parts.type-filter', ['checked' => !$hasTypes || in_array('chapter', $types), 'entity' => 'chapter', 'transKey' => 'chapter'])
<br>
- @include('search.form.type-filter', ['checked' => !$hasTypes || in_array('book', $types), 'entity' => 'book', 'transKey' => 'book'])
- @include('search.form.type-filter', ['checked' => !$hasTypes || in_array('bookshelf', $types), 'entity' => 'bookshelf', 'transKey' => 'shelf'])
+ @include('search.parts.type-filter', ['checked' => !$hasTypes || in_array('book', $types), 'entity' => 'book', 'transKey' => 'book'])
+ @include('search.parts.type-filter', ['checked' => !$hasTypes || in_array('bookshelf', $types), 'entity' => 'bookshelf', 'transKey' => 'shelf'])
</div>
<h6>{{ trans('entities.search_exact_matches') }}</h6>
- @include('search.form.term-list', ['type' => 'exact', 'currentList' => $options->exacts])
+ @include('search.parts.term-list', ['type' => 'exact', 'currentList' => $options->exacts])
<h6>{{ trans('entities.search_tags') }}</h6>
- @include('search.form.term-list', ['type' => 'tags', 'currentList' => $options->tags])
+ @include('search.parts.term-list', ['type' => 'tags', 'currentList' => $options->tags])
@if(signedInUser())
<h6>{{ trans('entities.search_options') }}</h6>
- @component('search.form.boolean-filter', ['filters' => $options->filters, 'name' => 'viewed_by_me', 'value' => null])
+ @component('search.parts.boolean-filter', ['filters' => $options->filters, 'name' => 'viewed_by_me', 'value' => null])
{{ trans('entities.search_viewed_by_me') }}
@endcomponent
- @component('search.form.boolean-filter', ['filters' => $options->filters, 'name' => 'not_viewed_by_me', 'value' => null])
+ @component('search.parts.boolean-filter', ['filters' => $options->filters, 'name' => 'not_viewed_by_me', 'value' => null])
{{ trans('entities.search_not_viewed_by_me') }}
@endcomponent
- @component('search.form.boolean-filter', ['filters' => $options->filters, 'name' => 'is_restricted', 'value' => null])
+ @component('search.parts.boolean-filter', ['filters' => $options->filters, 'name' => 'is_restricted', 'value' => null])
{{ trans('entities.search_permissions_set') }}
@endcomponent
- @component('search.form.boolean-filter', ['filters' => $options->filters, 'name' => 'created_by', 'value' => 'me'])
+ @component('search.parts.boolean-filter', ['filters' => $options->filters, 'name' => 'created_by', 'value' => 'me'])
{{ trans('entities.search_created_by_me') }}
@endcomponent
- @component('search.form.boolean-filter', ['filters' => $options->filters, 'name' => 'updated_by', 'value' => 'me'])
+ @component('search.parts.boolean-filter', ['filters' => $options->filters, 'name' => 'updated_by', 'value' => 'me'])
{{ trans('entities.search_updated_by_me') }}
@endcomponent
- @component('search.form.boolean-filter', ['filters' => $options->filters, 'name' => 'owned_by', 'value' => 'me'])
+ @component('search.parts.boolean-filter', ['filters' => $options->filters, 'name' => 'owned_by', 'value' => 'me'])
{{ trans('entities.search_owned_by_me') }}
@endcomponent
@endif
<h6>{{ trans('entities.search_date_options') }}</h6>
- @include('search.form.date-filter', ['name' => 'updated_after', 'filters' => $options->filters])
- @include('search.form.date-filter', ['name' => 'updated_before', 'filters' => $options->filters])
- @include('search.form.date-filter', ['name' => 'created_after', 'filters' => $options->filters])
- @include('search.form.date-filter', ['name' => 'created_before', 'filters' => $options->filters])
+ @include('search.parts.date-filter', ['name' => 'updated_after', 'filters' => $options->filters])
+ @include('search.parts.date-filter', ['name' => 'updated_before', 'filters' => $options->filters])
+ @include('search.parts.date-filter', ['name' => 'created_after', 'filters' => $options->filters])
+ @include('search.parts.date-filter', ['name' => 'created_before', 'filters' => $options->filters])
<button type="submit" class="button">{{ trans('entities.search_update') }}</button>
</form>
<h6 class="text-muted">{{ trans_choice('entities.search_total_results_found', $totalResults, ['count' => $totalResults]) }}</h6>
<div class="book-contents">
- @include('partials.entity-list', ['entities' => $entities, 'showPath' => true, 'showTags' => true])
+ @include('entities.list', ['entities' => $entities, 'showPath' => true, 'showTags' => true])
</div>
@if($hasNextPage)
@if(count($entities) > 0)
@foreach($entities as $index => $entity)
- @include('partials.entity-list-item', ['entity' => $entity, 'showPath' => true])
+ @include('entities.list-item', ['entity' => $entity, 'showPath' => true])
@if($index !== count($entities) - 1)
<hr>
@endif
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container">
<div class="grid left-focus v-center no-row-gap">
<div class="py-m">
- @include('settings.navbar', ['selected' => 'audit'])
+ @include('settings.parts.navbar', ['selected' => 'audit'])
</div>
</div>
component="submit-on-change"
option:submit-on-change:filter='[name="user"]'>
<label for="owner">{{ trans('settings.audit_table_user') }}</label>
- @include('components.user-select', ['user' => $listDetails['user'] ? \BookStack\Auth\User::query()->find($listDetails['user']) : null, 'name' => 'user', 'compact' => true])
+ @include('form.user-select', ['user' => $listDetails['user'] ? \BookStack\Auth\User::query()->find($listDetails['user']) : null, 'name' => 'user', 'compact' => true])
</div>
</form>
</div>
<a href="{{ sortUrl('/settings/audit', $listDetails, ['sort' => 'key']) }}">{{ trans('settings.audit_table_event') }}</a>
</th>
<th>{{ trans('settings.audit_table_related') }}</th>
+ <th>{{ trans('settings.audit_table_ip') }}</th>
<th>
<a href="{{ sortUrl('/settings/audit', $listDetails, ['sort' => 'created_at']) }}">{{ trans('settings.audit_table_date') }}</a></th>
</tr>
@foreach($activities as $activity)
<tr>
<td>
- @include('partials.table-user', ['user' => $activity->user, 'user_id' => $activity->user_id])
+ @include('settings.parts.table-user', ['user' => $activity->user, 'user_id' => $activity->user_id])
</td>
<td>{{ $activity->type }}</td>
<td width="40%">
<div class="px-m">{{ $activity->detail }}</div>
@endif
</td>
+ <td>{{ $activity->ip }}</td>
<td>{{ $activity->created_at }}</td>
</tr>
@endforeach
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container small">
- @include('settings.navbar-with-version', ['selected' => 'settings'])
+ @include('settings.parts.navbar-with-version', ['selected' => 'settings'])
<div class="card content-wrap auto-height">
<h2 id="features" class="list-heading">{{ trans('settings.app_features_security') }}</h2>
@endif
</div>
<div>
- @include('components.toggle-switch', [
+ @include('form.toggle-switch', [
'name' => 'setting-app-public',
'value' => setting('app-public'),
'label' => trans('settings.app_public_access_toggle'),
<p class="small">{{ trans('settings.app_secure_images_desc') }}</p>
</div>
<div>
- @include('components.toggle-switch', [
+ @include('form.toggle-switch', [
'name' => 'setting-app-secure-images',
'value' => setting('app-secure-images'),
'label' => trans('settings.app_secure_images_toggle'),
<p class="small">{!! trans('settings.app_disable_comments_desc') !!}</p>
</div>
<div>
- @include('components.toggle-switch', [
+ @include('form.toggle-switch', [
'name' => 'setting-app-disable-comments',
'value' => setting('app-disable-comments'),
'label' => trans('settings.app_disable_comments_toggle'),
</div>
<div class="pt-xs">
<input type="text" value="{{ setting('app-name', 'BookStack') }}" name="setting-app-name" id="setting-app-name">
- @include('components.toggle-switch', [
+ @include('form.toggle-switch', [
'name' => 'setting-app-name-header',
'value' => setting('app-name-header'),
'label' => trans('settings.app_name_header'),
<p class="small">{!! trans('settings.app_logo_desc') !!}</p>
</div>
<div class="pt-xs">
- @include('components.image-picker', [
+ @include('form.image-picker', [
'removeName' => 'setting-app-logo',
'removeValue' => 'none',
'defaultImage' => url('/logo.png'),
</div>
<div class="grid half pt-m">
<div>
- @include('components.setting-entity-color-picker', ['type' => 'bookshelf'])
- @include('components.setting-entity-color-picker', ['type' => 'book'])
- @include('components.setting-entity-color-picker', ['type' => 'chapter'])
+ @include('settings.parts.setting-entity-color-picker', ['type' => 'bookshelf'])
+ @include('settings.parts.setting-entity-color-picker', ['type' => 'book'])
+ @include('settings.parts.setting-entity-color-picker', ['type' => 'chapter'])
</div>
<div>
- @include('components.setting-entity-color-picker', ['type' => 'page'])
- @include('components.setting-entity-color-picker', ['type' => 'page-draft'])
+ @include('settings.parts.setting-entity-color-picker', ['type' => 'page'])
+ @include('settings.parts.setting-entity-color-picker', ['type' => 'page-draft'])
</div>
</div>
</div>
</select>
<div page-picker-container style="display: none;" class="mt-m">
- @include('components.page-picker', ['name' => 'setting-app-homepage', 'placeholder' => trans('settings.app_homepage_select'), 'value' => setting('app-homepage')])
+ @include('settings.parts.page-picker', ['name' => 'setting-app-homepage', 'placeholder' => trans('settings.app_homepage_select'), 'value' => setting('app-homepage')])
</div>
</div>
</div>
<div>
<label for="setting-app-privacy-link" class="setting-list-label">{{ trans('settings.app_footer_links') }}</label>
<p class="small mb-m">{{ trans('settings.app_footer_links_desc') }}</p>
- @include('settings.footer-links', ['name' => 'setting-app-footer-links', 'value' => setting('app-footer-links', [])])
+ @include('settings.parts.footer-links', ['name' => 'setting-app-footer-links', 'value' => setting('app-footer-links', [])])
</div>
<p class="small">{!! trans('settings.reg_enable_desc') !!}</p>
</div>
<div>
- @include('components.toggle-switch', [
+ @include('form.toggle-switch', [
'name' => 'setting-registration-enabled',
'value' => setting('registration-enabled'),
'label' => trans('settings.reg_enable_toggle')
<p class="small">{{ trans('settings.reg_confirm_email_desc') }}</p>
</div>
<div>
- @include('components.toggle-switch', [
+ @include('form.toggle-switch', [
'name' => 'setting-registration-confirmation',
'value' => setting('registration-confirmation'),
'label' => trans('settings.reg_email_confirmation_toggle')
</div>
- @include('components.entity-selector-popup', ['entityTypes' => 'page'])
+ @include('entities.selector-popup', ['entityTypes' => 'page'])
@stop
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container small">
- @include('settings.navbar-with-version', ['selected' => 'maintenance'])
+ @include('settings.parts.navbar-with-version', ['selected' => 'maintenance'])
<div class="card content-wrap auto-height pb-xl">
<h2 class="list-heading">{{ trans('settings.recycle_bin') }}</h2>
--}}
<div class="flex-container-row v-center wrap">
<div class="py-m flex fit-content">
- @include('settings.navbar', ['selected' => $selected])
+ @include('settings.parts.navbar', ['selected' => $selected])
</div>
<div class="flex"></div>
<div class="text-right p-m flex fit-content">
+++ /dev/null
-@include('partials.entity-display-item', ['entity' => $entity])
-@if($entity->isA('book'))
- @foreach($entity->chapters()->withTrashed()->get() as $chapter)
- @include('partials.entity-display-item', ['entity' => $chapter])
- @endforeach
-@endif
-@if($entity->isA('book') || $entity->isA('chapter'))
- @foreach($entity->pages()->withTrashed()->get() as $page)
- @include('partials.entity-display-item', ['entity' => $page])
- @endforeach
-@endif
\ No newline at end of file
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container small">
<div class="py-m">
- @include('settings.navbar', ['selected' => 'maintenance'])
+ @include('settings.parts.navbar', ['selected' => 'maintenance'])
</div>
<div class="card content-wrap auto-height">
@if($deletion->deletable instanceof \BookStack\Entities\Models\Entity)
<hr class="mt-m">
<h5>{{ trans('settings.recycle_bin_destroy_list') }}</h5>
- @include('settings.recycle-bin.deletable-entity-list', ['entity' => $deletion->deletable])
+ @include('settings.recycle-bin.parts.deletable-entity-list', ['entity' => $deletion->deletable])
@endif
</div>
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container">
<div class="py-m">
- @include('settings.navbar', ['selected' => 'maintenance'])
+ @include('settings.parts.navbar', ['selected' => 'maintenance'])
</div>
<div class="card content-wrap auto-height">
</div>
@endif
</td>
- <td>@include('partials.table-user', ['user' => $deletion->deleter, 'user_id' => $deletion->deleted_by])</td>
+ <td>@include('settings.parts.table-user', ['user' => $deletion->deleter, 'user_id' => $deletion->deleted_by])</td>
<td width="200">{{ $deletion->created_at }}</td>
<td width="150" class="text-right">
<div component="dropdown" class="dropdown-container">
--- /dev/null
+@include('settings.recycle-bin.parts.entity-display-item', ['entity' => $entity])
+@if($entity->isA('book'))
+ @foreach($entity->chapters()->withTrashed()->get() as $chapter)
+ @include('settings.recycle-bin.parts.entity-display-item', ['entity' => $chapter])
+ @endforeach
+@endif
+@if($entity->isA('book') || $entity->isA('chapter'))
+ @foreach($entity->pages()->withTrashed()->get() as $page)
+ @include('settings.recycle-bin.parts.entity-display-item', ['entity' => $page])
+ @endforeach
+@endif
\ No newline at end of file
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container small">
<div class="py-m">
- @include('settings.navbar', ['selected' => 'maintenance'])
+ @include('settings.parts.navbar', ['selected' => 'maintenance'])
</div>
<div class="card content-wrap auto-height">
@endif
</div>
- @include('settings.recycle-bin.deletable-entity-list', ['entity' => $deletion->deletable])
+ @include('settings.recycle-bin.parts.deletable-entity-list', ['entity' => $deletion->deletable])
@endif
</div>
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container small">
<div class="py-m">
- @include('settings.navbar', ['selected' => 'roles'])
+ @include('settings.parts.navbar', ['selected' => 'roles'])
</div>
<form action="{{ url("/settings/roles/new") }}" method="POST">
- @include('settings.roles.form', ['title' => trans('settings.role_create')])
+ @include('settings.roles.parts.form', ['title' => trans('settings.role_create')])
</form>
</div>
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container small">
<div class="py-m">
- @include('settings.navbar', ['selected' => 'roles'])
+ @include('settings.parts.navbar', ['selected' => 'roles'])
</div>
<div class="card content-wrap auto-height">
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container small">
<div class="py-m">
- @include('settings.navbar', ['selected' => 'roles'])
+ @include('settings.parts.navbar', ['selected' => 'roles'])
</div>
<form action="{{ url("/settings/roles/{$role->id}") }}" method="POST">
<input type="hidden" name="_method" value="PUT">
- @include('settings.roles.form', ['model' => $role, 'title' => trans('settings.role_edit'), 'icon' => 'edit'])
+ @include('settings.roles.parts.form', ['model' => $role, 'title' => trans('settings.role_edit'), 'icon' => 'edit'])
</form>
</div>
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container small">
<div class="py-m">
- @include('settings.navbar', ['selected' => 'roles'])
+ @include('settings.parts.navbar', ['selected' => 'roles'])
</div>
<div class="card content-wrap auto-height">
@foreach($roles as $role)
<tr>
<td><a href="{{ url("/settings/roles/{$role->id}") }}">{{ $role->display_name }}</a></td>
- <td>{{ $role->description }}</td>
+ <td>
+ @if($role->mfa_enforced)
+ <span title="{{ trans('settings.role_mfa_enforced') }}">@icon('lock') </span>
+ @endif
+ {{ $role->description }}
+ </td>
<td class="text-center">{{ $role->users->count() }}</td>
</tr>
@endforeach
-@include('components.custom-checkbox', [
+@include('form.custom-checkbox', [
'name' => 'permissions[' . $permission . ']',
'value' => 'true',
'checked' => old('permissions'.$permission, false)|| (!old('display_name', false) && (isset($role) && $role->hasPermission($permission))),
</div>
<div>
<div class="form-group">
- <label for="name">{{ trans('settings.role_name') }}</label>
+ <label for="display_name">{{ trans('settings.role_name') }}</label>
@include('form.text', ['name' => 'display_name'])
</div>
<div class="form-group">
- <label for="name">{{ trans('settings.role_desc') }}</label>
+ <label for="description">{{ trans('settings.role_desc') }}</label>
@include('form.text', ['name' => 'description'])
</div>
+ <div class="form-group">
+ @include('form.checkbox', ['name' => 'mfa_enforced', 'label' => trans('settings.role_mfa_enforced') ])
+ </div>
@if(config('auth.method') === 'ldap' || config('auth.method') === 'saml2')
<div class="form-group">
<div class="toggle-switch-list grid half mt-m">
<div>
- <div>@include('settings.roles.checkbox', ['permission' => 'restrictions-manage-all', 'label' => trans('settings.role_manage_entity_permissions')])</div>
- <div>@include('settings.roles.checkbox', ['permission' => 'restrictions-manage-own', 'label' => trans('settings.role_manage_own_entity_permissions')])</div>
- <div>@include('settings.roles.checkbox', ['permission' => 'templates-manage', 'label' => trans('settings.role_manage_page_templates')])</div>
- <div>@include('settings.roles.checkbox', ['permission' => 'access-api', 'label' => trans('settings.role_access_api')])</div>
+ <div>@include('settings.roles.parts.checkbox', ['permission' => 'restrictions-manage-all', 'label' => trans('settings.role_manage_entity_permissions')])</div>
+ <div>@include('settings.roles.parts.checkbox', ['permission' => 'restrictions-manage-own', 'label' => trans('settings.role_manage_own_entity_permissions')])</div>
+ <div>@include('settings.roles.parts.checkbox', ['permission' => 'templates-manage', 'label' => trans('settings.role_manage_page_templates')])</div>
+ <div>@include('settings.roles.parts.checkbox', ['permission' => 'access-api', 'label' => trans('settings.role_access_api')])</div>
+ <div>@include('settings.roles.parts.checkbox', ['permission' => 'content-export', 'label' => trans('settings.role_export_content')])</div>
</div>
<div>
- <div>@include('settings.roles.checkbox', ['permission' => 'settings-manage', 'label' => trans('settings.role_manage_settings')])</div>
- <div>@include('settings.roles.checkbox', ['permission' => 'users-manage', 'label' => trans('settings.role_manage_users')])</div>
- <div>@include('settings.roles.checkbox', ['permission' => 'user-roles-manage', 'label' => trans('settings.role_manage_roles')])</div>
+ <div>@include('settings.roles.parts.checkbox', ['permission' => 'settings-manage', 'label' => trans('settings.role_manage_settings')])</div>
+ <div>@include('settings.roles.parts.checkbox', ['permission' => 'users-manage', 'label' => trans('settings.role_manage_users')])</div>
+ <div>@include('settings.roles.parts.checkbox', ['permission' => 'user-roles-manage', 'label' => trans('settings.role_manage_roles')])</div>
<p class="text-warn text-small mt-s mb-none">{{ trans('settings.roles_system_warning') }}</p>
</div>
</div>
<a href="#" permissions-table-toggle-all-in-row class="text-small text-primary">{{ trans('common.toggle_all') }}</a>
</td>
<td>
- @include('settings.roles.checkbox', ['permission' => 'bookshelf-create-all', 'label' => trans('settings.role_all')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'bookshelf-create-all', 'label' => trans('settings.role_all')])
</td>
<td>
- @include('settings.roles.checkbox', ['permission' => 'bookshelf-view-own', 'label' => trans('settings.role_own')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'bookshelf-view-own', 'label' => trans('settings.role_own')])
<br>
- @include('settings.roles.checkbox', ['permission' => 'bookshelf-view-all', 'label' => trans('settings.role_all')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'bookshelf-view-all', 'label' => trans('settings.role_all')])
</td>
<td>
- @include('settings.roles.checkbox', ['permission' => 'bookshelf-update-own', 'label' => trans('settings.role_own')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'bookshelf-update-own', 'label' => trans('settings.role_own')])
<br>
- @include('settings.roles.checkbox', ['permission' => 'bookshelf-update-all', 'label' => trans('settings.role_all')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'bookshelf-update-all', 'label' => trans('settings.role_all')])
</td>
<td>
- @include('settings.roles.checkbox', ['permission' => 'bookshelf-delete-own', 'label' => trans('settings.role_own')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'bookshelf-delete-own', 'label' => trans('settings.role_own')])
<br>
- @include('settings.roles.checkbox', ['permission' => 'bookshelf-delete-all', 'label' => trans('settings.role_all')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'bookshelf-delete-all', 'label' => trans('settings.role_all')])
</td>
</tr>
<tr>
<a href="#" permissions-table-toggle-all-in-row class="text-small text-primary">{{ trans('common.toggle_all') }}</a>
</td>
<td>
- @include('settings.roles.checkbox', ['permission' => 'book-create-all', 'label' => trans('settings.role_all')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'book-create-all', 'label' => trans('settings.role_all')])
</td>
<td>
- @include('settings.roles.checkbox', ['permission' => 'book-view-own', 'label' => trans('settings.role_own')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'book-view-own', 'label' => trans('settings.role_own')])
<br>
- @include('settings.roles.checkbox', ['permission' => 'book-view-all', 'label' => trans('settings.role_all')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'book-view-all', 'label' => trans('settings.role_all')])
</td>
<td>
- @include('settings.roles.checkbox', ['permission' => 'book-update-own', 'label' => trans('settings.role_own')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'book-update-own', 'label' => trans('settings.role_own')])
<br>
- @include('settings.roles.checkbox', ['permission' => 'book-update-all', 'label' => trans('settings.role_all')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'book-update-all', 'label' => trans('settings.role_all')])
</td>
<td>
- @include('settings.roles.checkbox', ['permission' => 'book-delete-own', 'label' => trans('settings.role_own')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'book-delete-own', 'label' => trans('settings.role_own')])
<br>
- @include('settings.roles.checkbox', ['permission' => 'book-delete-all', 'label' => trans('settings.role_all')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'book-delete-all', 'label' => trans('settings.role_all')])
</td>
</tr>
<tr>
<a href="#" permissions-table-toggle-all-in-row class="text-small text-primary">{{ trans('common.toggle_all') }}</a>
</td>
<td>
- @include('settings.roles.checkbox', ['permission' => 'chapter-create-own', 'label' => trans('settings.role_own')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'chapter-create-own', 'label' => trans('settings.role_own')])
<br>
- @include('settings.roles.checkbox', ['permission' => 'chapter-create-all', 'label' => trans('settings.role_all')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'chapter-create-all', 'label' => trans('settings.role_all')])
</td>
<td>
- @include('settings.roles.checkbox', ['permission' => 'chapter-view-own', 'label' => trans('settings.role_own')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'chapter-view-own', 'label' => trans('settings.role_own')])
<br>
- @include('settings.roles.checkbox', ['permission' => 'chapter-view-all', 'label' => trans('settings.role_all')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'chapter-view-all', 'label' => trans('settings.role_all')])
</td>
<td>
- @include('settings.roles.checkbox', ['permission' => 'chapter-update-own', 'label' => trans('settings.role_own')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'chapter-update-own', 'label' => trans('settings.role_own')])
<br>
- @include('settings.roles.checkbox', ['permission' => 'chapter-update-all', 'label' => trans('settings.role_all')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'chapter-update-all', 'label' => trans('settings.role_all')])
</td>
<td>
- @include('settings.roles.checkbox', ['permission' => 'chapter-delete-own', 'label' => trans('settings.role_own')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'chapter-delete-own', 'label' => trans('settings.role_own')])
<br>
- @include('settings.roles.checkbox', ['permission' => 'chapter-delete-all', 'label' => trans('settings.role_all')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'chapter-delete-all', 'label' => trans('settings.role_all')])
</td>
</tr>
<tr>
<a href="#" permissions-table-toggle-all-in-row class="text-small text-primary">{{ trans('common.toggle_all') }}</a>
</td>
<td>
- @include('settings.roles.checkbox', ['permission' => 'page-create-own', 'label' => trans('settings.role_own')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'page-create-own', 'label' => trans('settings.role_own')])
<br>
- @include('settings.roles.checkbox', ['permission' => 'page-create-all', 'label' => trans('settings.role_all')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'page-create-all', 'label' => trans('settings.role_all')])
</td>
<td>
- @include('settings.roles.checkbox', ['permission' => 'page-view-own', 'label' => trans('settings.role_own')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'page-view-own', 'label' => trans('settings.role_own')])
<br>
- @include('settings.roles.checkbox', ['permission' => 'page-view-all', 'label' => trans('settings.role_all')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'page-view-all', 'label' => trans('settings.role_all')])
</td>
<td>
- @include('settings.roles.checkbox', ['permission' => 'page-update-own', 'label' => trans('settings.role_own')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'page-update-own', 'label' => trans('settings.role_own')])
<br>
- @include('settings.roles.checkbox', ['permission' => 'page-update-all', 'label' => trans('settings.role_all')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'page-update-all', 'label' => trans('settings.role_all')])
</td>
<td>
- @include('settings.roles.checkbox', ['permission' => 'page-delete-own', 'label' => trans('settings.role_own')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'page-delete-own', 'label' => trans('settings.role_own')])
<br>
- @include('settings.roles.checkbox', ['permission' => 'page-delete-all', 'label' => trans('settings.role_all')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'page-delete-all', 'label' => trans('settings.role_all')])
</td>
</tr>
<tr>
<div>{{ trans('entities.images') }}</div>
<a href="#" permissions-table-toggle-all-in-row class="text-small text-primary">{{ trans('common.toggle_all') }}</a>
</td>
- <td>@include('settings.roles.checkbox', ['permission' => 'image-create-all', 'label' => ''])</td>
+ <td>@include('settings.roles.parts.checkbox', ['permission' => 'image-create-all', 'label' => ''])</td>
<td style="line-height:1.2;"><small class="faded">{{ trans('settings.role_controlled_by_asset') }}</small></td>
<td>
- @include('settings.roles.checkbox', ['permission' => 'image-update-own', 'label' => trans('settings.role_own')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'image-update-own', 'label' => trans('settings.role_own')])
<br>
- @include('settings.roles.checkbox', ['permission' => 'image-update-all', 'label' => trans('settings.role_all')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'image-update-all', 'label' => trans('settings.role_all')])
</td>
<td>
- @include('settings.roles.checkbox', ['permission' => 'image-delete-own', 'label' => trans('settings.role_own')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'image-delete-own', 'label' => trans('settings.role_own')])
<br>
- @include('settings.roles.checkbox', ['permission' => 'image-delete-all', 'label' => trans('settings.role_all')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'image-delete-all', 'label' => trans('settings.role_all')])
</td>
</tr>
<tr>
<div>{{ trans('entities.attachments') }}</div>
<a href="#" permissions-table-toggle-all-in-row class="text-small text-primary">{{ trans('common.toggle_all') }}</a>
</td>
- <td>@include('settings.roles.checkbox', ['permission' => 'attachment-create-all', 'label' => ''])</td>
+ <td>@include('settings.roles.parts.checkbox', ['permission' => 'attachment-create-all', 'label' => ''])</td>
<td style="line-height:1.2;"><small class="faded">{{ trans('settings.role_controlled_by_asset') }}</small></td>
<td>
- @include('settings.roles.checkbox', ['permission' => 'attachment-update-own', 'label' => trans('settings.role_own')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'attachment-update-own', 'label' => trans('settings.role_own')])
<br>
- @include('settings.roles.checkbox', ['permission' => 'attachment-update-all', 'label' => trans('settings.role_all')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'attachment-update-all', 'label' => trans('settings.role_all')])
</td>
<td>
- @include('settings.roles.checkbox', ['permission' => 'attachment-delete-own', 'label' => trans('settings.role_own')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'attachment-delete-own', 'label' => trans('settings.role_own')])
<br>
- @include('settings.roles.checkbox', ['permission' => 'attachment-delete-all', 'label' => trans('settings.role_all')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'attachment-delete-all', 'label' => trans('settings.role_all')])
</td>
</tr>
<tr>
<div>{{ trans('entities.comments') }}</div>
<a href="#" permissions-table-toggle-all-in-row class="text-small text-primary">{{ trans('common.toggle_all') }}</a>
</td>
- <td>@include('settings.roles.checkbox', ['permission' => 'comment-create-all', 'label' => ''])</td>
+ <td>@include('settings.roles.parts.checkbox', ['permission' => 'comment-create-all', 'label' => ''])</td>
<td style="line-height:1.2;"><small class="faded">{{ trans('settings.role_controlled_by_asset') }}</small></td>
<td>
- @include('settings.roles.checkbox', ['permission' => 'comment-update-own', 'label' => trans('settings.role_own')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'comment-update-own', 'label' => trans('settings.role_own')])
<br>
- @include('settings.roles.checkbox', ['permission' => 'comment-update-all', 'label' => trans('settings.role_all')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'comment-update-all', 'label' => trans('settings.role_all')])
</td>
<td>
- @include('settings.roles.checkbox', ['permission' => 'comment-delete-own', 'label' => trans('settings.role_own')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'comment-delete-own', 'label' => trans('settings.role_own')])
<br>
- @include('settings.roles.checkbox', ['permission' => 'comment-delete-all', 'label' => trans('settings.role_all')])
+ @include('settings.roles.parts.checkbox', ['permission' => 'comment-delete-all', 'label' => trans('settings.role_all')])
</td>
</tr>
</table>
<div class="card content-wrap auto-height">
<h2 class="list-heading">{{ trans('settings.role_users') }}</h2>
- @if(isset($role) && count($role->users) > 0)
+ @if(count($role->users ?? []) > 0)
<div class="grid third">
@foreach($role->users as $user)
<div class="user-list-item">
+++ /dev/null
-<div class="breadcrumbs">
- <a href="{{$shelf->getUrl()}}" class="text-bookshelf text-button">@icon('bookshelf'){{ $shelf->getShortName() }}</a>
-</div>
\ No newline at end of file
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container small">
<div class="my-s">
- @include('partials.breadcrumbs', ['crumbs' => [
+ @include('entities.breadcrumbs', ['crumbs' => [
'/shelves' => [
'text' => trans('entities.shelves'),
'icon' => 'bookshelf',
<main class="card content-wrap">
<h1 class="list-heading">{{ trans('entities.shelves_create') }}</h1>
<form action="{{ url("/shelves") }}" method="POST" enctype="multipart/form-data">
- @include('shelves.form', ['shelf' => null, 'books' => $books])
+ @include('shelves.parts.form', ['shelf' => null, 'books' => $books])
</form>
</main>
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container small">
<div class="my-s">
- @include('partials.breadcrumbs', ['crumbs' => [
+ @include('entities.breadcrumbs', ['crumbs' => [
$shelf,
$shelf->getUrl('/delete') => [
'text' => trans('entities.shelves_delete'),
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container small">
<div class="my-s">
- @include('partials.breadcrumbs', ['crumbs' => [
+ @include('entities.breadcrumbs', ['crumbs' => [
$shelf,
$shelf->getUrl('/edit') => [
'text' => trans('entities.shelves_edit'),
<h1 class="list-heading">{{ trans('entities.shelves_edit') }}</h1>
<form action="{{ $shelf->getUrl() }}" method="POST" enctype="multipart/form-data">
<input type="hidden" name="_method" value="PUT">
- @include('shelves.form', ['model' => $shelf])
+ @include('shelves.parts.form', ['model' => $shelf])
</form>
</main>
</div>
-@extends('tri-layout')
+@extends('layouts.tri')
@section('body')
- @include('shelves.list', ['shelves' => $shelves, 'view' => $view])
+ @include('shelves.parts.list', ['shelves' => $shelves, 'view' => $view])
@stop
@section('right')
<span>{{ trans('entities.shelves_new_action') }}</span>
</a>
@endif
- @include('partials.view-toggle', ['view' => $view, 'type' => 'shelves'])
+ @include('entities.view-toggle', ['view' => $view, 'type' => 'shelves'])
</div>
</div>
@if($recents)
<div id="recents" class="mb-xl">
<h5>{{ trans('entities.recently_viewed') }}</h5>
- @include('partials.entity-list', ['entities' => $recents, 'style' => 'compact'])
+ @include('entities.list', ['entities' => $recents, 'style' => 'compact'])
</div>
@endif
<div id="popular" class="mb-xl">
<h5>{{ trans('entities.shelves_popular') }}</h5>
@if(count($popular) > 0)
- @include('partials.entity-list', ['entities' => $popular, 'style' => 'compact'])
+ @include('entities.list', ['entities' => $popular, 'style' => 'compact'])
@else
<div class="text-muted">{{ trans('entities.shelves_popular_empty') }}</div>
@endif
<div id="new" class="mb-xl">
<h5>{{ trans('entities.shelves_new') }}</h5>
@if(count($new) > 0)
- @include('partials.entity-list', ['entities' => $new, 'style' => 'compact'])
+ @include('entities.list', ['entities' => $new, 'style' => 'compact'])
@else
<div class="text-muted">{{ trans('entities.shelves_new_empty') }}</div>
@endif
<div class="collapse-content" collapsible-content>
<p class="small">{{ trans('common.cover_image_description') }}</p>
- @include('components.image-picker', [
+ @include('form.image-picker', [
'defaultImage' => url('/book_default_cover.png'),
'currentImage' => (isset($shelf) && $shelf->cover) ? $shelf->getBookCover() : url('/book_default_cover.png') ,
'name' => 'image',
<label for="tag-manager">{{ trans('entities.shelf_tags') }}</label>
</button>
<div class="collapse-content" collapsible-content>
- @include('components.tag-manager', ['entity' => $shelf ?? null])
+ @include('entities.tag-manager', ['entity' => $shelf ?? null])
</div>
</div>
<div class="grid half v-center">
<h1 class="list-heading">{{ trans('entities.shelves') }}</h1>
<div class="text-right">
- @include('partials.sort', ['options' => $sortOptions, 'order' => $order, 'sort' => $sort, 'type' => 'bookshelves'])
+ @include('entities.sort', ['options' => $sortOptions, 'order' => $order, 'sort' => $sort, 'type' => 'bookshelves'])
</div>
</div>
@if ($index !== 0)
<hr class="my-m">
@endif
- @include('shelves.list-item', ['shelf' => $shelf])
+ @include('shelves.parts.list-item', ['shelf' => $shelf])
@endforeach
</div>
@else
<div class="grid third">
@foreach($shelves as $key => $shelf)
- @include('partials.entity-grid-item', ['entity' => $shelf])
+ @include('entities.grid-item', ['entity' => $shelf])
@endforeach
</div>
@endif
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container small">
<div class="my-s">
- @include('partials.breadcrumbs', ['crumbs' => [
+ @include('entities.breadcrumbs', ['crumbs' => [
$shelf,
$shelf->getUrl('/permissions') => [
'text' => trans('entities.shelves_permissions'),
]])
</div>
- <div class="card content-wrap">
+ <div class="card content-wrap auto-height">
<h1 class="list-heading">{{ trans('entities.shelves_permissions') }}</h1>
@include('form.entity-permissions', ['model' => $shelf])
</div>
-@extends('tri-layout')
+@extends('layouts.tri')
@push('social-meta')
<meta property="og:description" content="{{ Str::limit($shelf->description, 100, '...') }}">
@section('body')
<div class="mb-s">
- @include('partials.breadcrumbs', ['crumbs' => [
+ @include('entities.breadcrumbs', ['crumbs' => [
$shelf,
]])
</div>
<h1 class="flex fit-content break-text">{{ $shelf->name }}</h1>
<div class="flex"></div>
<div class="flex fit-content text-m-right my-m ml-m">
- @include('partials.sort', ['options' => [
+ @include('entities.sort', ['options' => [
'default' => trans('common.sort_default'),
'name' => trans('common.sort_name'),
'created_at' => trans('common.sort_created_at'),
@if($view === 'list')
<div class="entity-list">
@foreach($sortedVisibleShelfBooks as $book)
- @include('books.list-item', ['book' => $book])
+ @include('books.parts.list-item', ['book' => $book])
@endforeach
</div>
@else
<div class="grid third">
@foreach($sortedVisibleShelfBooks as $book)
- @include('partials.entity-grid-item', ['entity' => $book])
+ @include('entities.grid-item', ['entity' => $book])
@endforeach
</div>
@endif
@if($shelf->tags->count() > 0)
<div id="tags" class="mb-xl">
- @include('components.tag-list', ['entity' => $shelf])
+ @include('entities.tag-list', ['entity' => $shelf])
</div>
@endif
<div id="details" class="mb-xl">
<h5>{{ trans('common.details') }}</h5>
<div class="text-small text-muted blended-links">
- @include('partials.entity-meta', ['entity' => $shelf])
+ @include('entities.meta', ['entity' => $shelf])
@if($shelf->restricted)
<div class="active-restriction">
@if(userCan('restrictions-manage', $shelf))
@if(count($activity) > 0)
<div class="mb-xl">
<h5>{{ trans('entities.recent_activity') }}</h5>
- @include('partials.activity-list', ['activity' => $activity])
+ @include('common.activity-list', ['activity' => $activity])
</div>
@endif
@stop
</a>
@endif
- @include('partials.view-toggle', ['view' => $view, 'type' => 'shelf'])
+ @include('entities.view-toggle', ['view' => $view, 'type' => 'shelf'])
<hr class="primary-background">
@if(signedInUser())
<hr class="primary-background">
- @include('partials.entity-favourite-action', ['entity' => $shelf])
+ @include('entities.favourite-action', ['entity' => $shelf])
@endif
</div>
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
{!! csrf_field() !!}
<div class="setting-list">
- @include('users.api-tokens.form')
+ @include('users.api-tokens.parts.form')
<div>
<p class="text-warn italic">
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container small pt-xl">
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
</div>
@endif
- @include('users.api-tokens.form', ['model' => $token])
+ @include('users.api-tokens.parts.form', ['model' => $token])
</div>
<div class="grid half gap-xl v-center">
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container small">
<div class="py-m">
- @include('settings.navbar', ['selected' => 'users'])
+ @include('settings.parts.navbar', ['selected' => 'users'])
</div>
<main class="card content-wrap">
{!! csrf_field() !!}
<div class="setting-list">
- @include('users.form')
+ @include('users.parts.form')
</div>
<div class="form-group text-right">
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container small">
<div class="py-m">
- @include('settings.navbar', ['selected' => 'users'])
+ @include('settings.parts.navbar', ['selected' => 'users'])
</div>
<div class="card content-wrap auto-height">
<p class="small">{{ trans('settings.users_migrate_ownership_desc') }}</p>
</div>
<div>
- @include('components.user-select', ['name' => 'new_owner_id', 'user' => null, 'compact' => false])
+ @include('form.user-select', ['name' => 'new_owner_id', 'user' => null, 'compact' => false])
</div>
</div>
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container small">
<div class="py-m">
- @include('settings.navbar', ['selected' => 'users'])
+ @include('settings.parts.navbar', ['selected' => 'users'])
</div>
<section class="card content-wrap">
<input type="hidden" name="_method" value="PUT">
<div class="setting-list">
- @include('users.form', ['model' => $user, 'authMethod' => $authMethod])
+ @include('users.parts.form', ['model' => $user, 'authMethod' => $authMethod])
<div class="grid half gap-xl">
<div>
<p class="small">{{ trans('settings.users_avatar_desc') }}</p>
</div>
<div>
- @include('components.image-picker', [
+ @include('form.image-picker', [
'resizeHeight' => '512',
'resizeWidth' => '512',
'showRemove' => false,
</form>
</section>
+ <section class="card content-wrap auto-height">
+ <h2 class="list-heading">{{ trans('settings.users_mfa') }}</h2>
+ <p>{{ trans('settings.users_mfa_desc') }}</p>
+ <div class="grid half gap-xl v-center pb-s">
+ <div>
+ @if ($mfaMethods->count() > 0)
+ <span class="text-pos">@icon('check-circle')</span>
+ @else
+ <span class="text-neg">@icon('cancel')</span>
+ @endif
+ {{ trans_choice('settings.users_mfa_x_methods', $mfaMethods->count()) }}
+ </div>
+ <div class="text-m-right">
+ @if($user->id === user()->id)
+ <a href="{{ url('/mfa/setup') }}" class="button outline">{{ trans('settings.users_mfa_configure') }}</a>
+ @endif
+ </div>
+ </div>
+
+ </section>
+
@if(user()->id === $user->id && count($activeSocialDrivers) > 0)
<section class="card content-wrap auto-height">
<h2 class="list-heading">{{ trans('settings.users_social_accounts') }}</h2>
@endif
@if((user()->id === $user->id && userCan('access-api')) || userCan('users-manage'))
- @include('users.api-tokens.list', ['user' => $user])
+ @include('users.api-tokens.parts.list', ['user' => $user])
@endif
</div>
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div class="container small">
<div class="py-m">
- @include('settings.navbar', ['selected' => 'users'])
+ @include('settings.parts.navbar', ['selected' => 'users'])
</div>
<main class="card content-wrap">
<td class="text-center" style="line-height: 0;"><img class="avatar med" src="{{ $user->getAvatar(40)}}" alt="{{ $user->name }}"></td>
<td>
<a href="{{ url("/settings/users/{$user->id}") }}">
- {{ $user->name }} <br> <span class="text-muted">{{ $user->email }}</span>
+ {{ $user->name }}
+ <br>
+ <span class="text-muted">{{ $user->email }}</span>
+ @if($user->mfa_values_count > 0)
+ <span title="MFA Configured" class="text-pos">@icon('lock')</span>
+ @endif
</a>
</td>
<td>
{{ trans('settings.users_send_invite_text') }}
</p>
- @include('components.toggle-switch', [
+ @include('form.toggle-switch', [
'name' => 'send_invite',
'value' => old('send_invite', 'true') === 'true',
'label' => trans('settings.users_send_invite_option')
-@extends('simple-layout')
+@extends('layouts.simple')
@section('body')
<div>
<section id="recent-user-activity" class="mb-xl">
<h5>{{ trans('entities.recent_activity') }}</h5>
- @include('partials.activity-list', ['activity' => $activity])
+ @include('common.activity-list', ['activity' => $activity])
</section>
</div>
@endif
</h2>
@if (count($recentlyCreated['pages']) > 0)
- @include('partials.entity-list', ['entities' => $recentlyCreated['pages'], 'showPath' => true])
+ @include('entities.list', ['entities' => $recentlyCreated['pages'], 'showPath' => true])
@else
<p class="text-muted">{{ trans('entities.profile_not_created_pages', ['userName' => $user->name]) }}</p>
@endif
@endif
</h2>
@if (count($recentlyCreated['chapters']) > 0)
- @include('partials.entity-list', ['entities' => $recentlyCreated['chapters'], 'showPath' => true])
+ @include('entities.list', ['entities' => $recentlyCreated['chapters'], 'showPath' => true])
@else
<p class="text-muted">{{ trans('entities.profile_not_created_chapters', ['userName' => $user->name]) }}</p>
@endif
@endif
</h2>
@if (count($recentlyCreated['books']) > 0)
- @include('partials.entity-list', ['entities' => $recentlyCreated['books'], 'showPath' => true])
+ @include('entities.list', ['entities' => $recentlyCreated['books'], 'showPath' => true])
@else
<p class="text-muted">{{ trans('entities.profile_not_created_books', ['userName' => $user->name]) }}</p>
@endif
@endif
</h2>
@if (count($recentlyCreated['shelves']) > 0)
- @include('partials.entity-list', ['entities' => $recentlyCreated['shelves'], 'showPath' => true])
+ @include('entities.list', ['entities' => $recentlyCreated['shelves'], 'showPath' => true])
@else
<p class="text-muted">{{ trans('entities.profile_not_created_shelves', ['userName' => $user->name]) }}</p>
@endif
* Routes have a uri prefix of /api/.
* Controllers are all within app/Http/Controllers/Api.
*/
-Route::get('docs', 'ApiDocsController@display');
Route::get('docs.json', 'ApiDocsController@json');
Route::get('books', 'BookApiController@list');
<?php
Route::get('/status', 'StatusController@show');
-Route::get('/robots.txt', 'HomeController@getRobots');
+Route::get('/robots.txt', 'HomeController@robots');
// Authenticated routes...
Route::group(['middleware' => 'auth'], function () {
Route::get('/uploads/images/{path}', 'Images\ImageController@showImage')
->where('path', '.*$');
+ // API docs routes
+ Route::get('/api/docs', 'Api\ApiDocsController@display');
+
Route::get('/pages/recently-updated', 'PageController@showRecentlyUpdated');
// Shelves
});
});
+// MFA routes
+Route::group(['middleware' => 'mfa-setup'], function () {
+ Route::get('/mfa/setup', 'Auth\MfaController@setup');
+ Route::get('/mfa/totp/generate', 'Auth\MfaTotpController@generate');
+ Route::post('/mfa/totp/confirm', 'Auth\MfaTotpController@confirm');
+ Route::get('/mfa/backup_codes/generate', 'Auth\MfaBackupCodesController@generate');
+ Route::post('/mfa/backup_codes/confirm', 'Auth\MfaBackupCodesController@confirm');
+});
+Route::group(['middleware' => 'guest'], function () {
+ Route::get('/mfa/verify', 'Auth\MfaController@verify');
+ Route::post('/mfa/totp/verify', 'Auth\MfaTotpController@verify');
+ Route::post('/mfa/backup_codes/verify', 'Auth\MfaBackupCodesController@verify');
+});
+Route::delete('/mfa/{method}/remove', 'Auth\MfaController@remove')->middleware('auth');
+
// Social auth routes
Route::get('/login/service/{socialDriver}', 'Auth\SocialController@login');
Route::get('/login/service/{socialDriver}/callback', 'Auth\SocialController@callback');
-Route::group(['middleware' => 'auth'], function () {
- Route::post('/login/service/{socialDriver}/detach', 'Auth\SocialController@detach');
-});
+Route::post('/login/service/{socialDriver}/detach', 'Auth\SocialController@detach')->middleware('auth');
Route::get('/register/service/{socialDriver}', 'Auth\SocialController@register');
// Login/Logout routes
Route::get('/password/reset/{token}', 'Auth\ResetPasswordController@showResetForm');
Route::post('/password/reset', 'Auth\ResetPasswordController@reset');
-Route::fallback('HomeController@getNotFound')->name('fallback');
+Route::fallback('HomeController@notFound')->name('fallback');
+++ /dev/null
-<?php
-
-namespace Tests;
-
-use BookStack\Entities\Models\Book;
-
-class ActivityTrackingTest extends BrowserKitTest
-{
- public function test_recently_viewed_books()
- {
- $books = Book::all()->take(10);
-
- $this->asAdmin()->visit('/books')
- ->dontSeeInElement('#recents', $books[0]->name)
- ->dontSeeInElement('#recents', $books[1]->name)
- ->visit($books[0]->getUrl())
- ->visit($books[1]->getUrl())
- ->visit('/books')
- ->seeInElement('#recents', $books[0]->name)
- ->seeInElement('#recents', $books[1]->name);
- }
-
- public function test_popular_books()
- {
- $books = Book::all()->take(10);
-
- $this->asAdmin()->visit('/books')
- ->dontSeeInElement('#popular', $books[0]->name)
- ->dontSeeInElement('#popular', $books[1]->name)
- ->visit($books[0]->getUrl())
- ->visit($books[1]->getUrl())
- ->visit($books[0]->getUrl())
- ->visit('/books')
- ->seeInNthElement('#popular .book', 0, $books[0]->name)
- ->seeInNthElement('#popular .book', 1, $books[1]->name);
- }
-}
namespace Tests\Api;
-use BookStack\Auth\User;
use Tests\TestCase;
class ApiDocsTest extends TestCase
protected $endpoint = '/api/docs';
- public function test_docs_page_not_visible_to_normal_viewers()
- {
- $viewer = $this->getViewer();
- $resp = $this->actingAs($viewer)->get($this->endpoint);
- $resp->assertStatus(403);
-
- $resp = $this->actingAsApiEditor()->get($this->endpoint);
- $resp->assertStatus(200);
- }
-
public function test_docs_page_returns_view_with_docs_content()
{
$resp = $this->actingAsApiEditor()->get($this->endpoint);
]],
]);
}
-
- public function test_docs_page_visible_by_public_user_if_given_permission()
- {
- $this->setSettings(['app-public' => true]);
- $guest = User::getDefault();
-
- $this->startSession();
- $resp = $this->get('/api/docs');
- $resp->assertStatus(403);
-
- $this->giveUserPermissions($guest, ['access-api']);
-
- $resp = $this->get('/api/docs');
- $resp->assertStatus(200);
- }
}
$resp->assertSee('# ' . $book->pages()->first()->name);
$resp->assertSee('# ' . $book->chapters()->first()->name);
}
+
+ public function test_cant_export_when_not_have_permission()
+ {
+ $types = ['html', 'plaintext', 'pdf', 'markdown'];
+ $this->actingAsApiEditor();
+ $this->removePermissionFromUser($this->getEditor(), 'content-export');
+
+ $book = Book::visible()->first();
+ foreach ($types as $type) {
+ $resp = $this->get($this->baseEndpoint . "/{$book->id}/export/{$type}");
+ $this->assertPermissionError($resp);
+ }
+ }
}
$resp->assertSee('# ' . $chapter->name);
$resp->assertSee('# ' . $chapter->pages()->first()->name);
}
+
+ public function test_cant_export_when_not_have_permission()
+ {
+ $types = ['html', 'plaintext', 'pdf', 'markdown'];
+ $this->actingAsApiEditor();
+ $this->removePermissionFromUser($this->getEditor(), 'content-export');
+
+ $chapter = Chapter::visible()->has('pages')->first();
+ foreach ($types as $type) {
+ $resp = $this->get($this->baseEndpoint . "/{$chapter->id}/export/{$type}");
+ $this->assertPermissionError($resp);
+ }
+ }
}
$resp->assertStatus(403);
}
+ public function test_update_endpoint_does_not_wipe_content_if_no_html_or_md_provided()
+ {
+ $this->actingAsApiEditor();
+ $page = Page::visible()->first();
+ $originalContent = $page->html;
+ $details = [
+ 'name' => 'My updated API page',
+ 'tags' => [
+ [
+ 'name' => 'freshtag',
+ 'value' => 'freshtagval',
+ ],
+ ],
+ ];
+
+ $this->putJson($this->baseEndpoint . "/{$page->id}", $details);
+ $page->refresh();
+
+ $this->assertEquals($originalContent, $page->html);
+ }
+
public function test_delete_endpoint()
{
$this->actingAsApiEditor();
$resp->assertSee('# ' . $page->name);
$resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.md"');
}
+
+ public function test_cant_export_when_not_have_permission()
+ {
+ $types = ['html', 'plaintext', 'pdf', 'markdown'];
+ $this->actingAsApiEditor();
+ $this->removePermissionFromUser($this->getEditor(), 'content-export');
+
+ $page = Page::visible()->first();
+ foreach ($types as $type) {
+ $resp = $this->get($this->baseEndpoint . "/{$page->id}/export/{$type}");
+ $this->assertPermissionError($resp);
+ }
+ }
}
$resp->assertSeeText($chapter->name);
$resp->assertDontSeeText($page->name);
}
+
+ public function test_ip_address_logged_and_visible()
+ {
+ config()->set('app.proxies', '*');
+ $editor = $this->getEditor();
+ /** @var Page $page */
+ $page = Page::query()->first();
+
+ $this->actingAs($editor)->put($page->getUrl(), [
+ 'name' => 'Updated page',
+ 'html' => '<p>Updated content</p>',
+ ], [
+ 'X-Forwarded-For' => '192.123.45.1',
+ ])->assertRedirect($page->refresh()->getUrl());
+
+ $this->assertDatabaseHas('activities', [
+ 'type' => ActivityType::PAGE_UPDATE,
+ 'ip' => '192.123.45.1',
+ 'user_id' => $editor->id,
+ 'entity_id' => $page->id,
+ ]);
+
+ $resp = $this->asAdmin()->get('/settings/audit');
+ $resp->assertSee('192.123.45.1');
+ }
+
+ public function test_ip_address_not_logged_in_demo_mode()
+ {
+ config()->set('app.proxies', '*');
+ config()->set('app.env', 'demo');
+ $editor = $this->getEditor();
+ /** @var Page $page */
+ $page = Page::query()->first();
+
+ $this->actingAs($editor)->put($page->getUrl(), [
+ 'name' => 'Updated page',
+ 'html' => '<p>Updated content</p>',
+ ], [
+ 'X-Forwarded-For' => '192.123.45.1',
+ 'REMOTE_ADDR' => '192.123.45.2',
+ ])->assertRedirect($page->refresh()->getUrl());
+
+ $this->assertDatabaseHas('activities', [
+ 'type' => ActivityType::PAGE_UPDATE,
+ 'ip' => '127.0.0.1',
+ 'user_id' => $editor->id,
+ 'entity_id' => $page->id,
+ ]);
+ }
}
namespace Tests\Auth;
-use BookStack\Auth\Role;
+use BookStack\Auth\Access\Mfa\MfaSession;
use BookStack\Auth\User;
use BookStack\Entities\Models\Page;
use BookStack\Notifications\ConfirmEmail;
use BookStack\Notifications\ResetPassword;
-use BookStack\Settings\SettingService;
-use DB;
-use Hash;
+use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Notification;
-use Illuminate\Support\Str;
-use Tests\BrowserKitTest;
+use Tests\TestCase;
+use Tests\TestResponse;
-class AuthTest extends BrowserKitTest
+class AuthTest extends TestCase
{
public function test_auth_working()
{
- $this->visit('/')
- ->seePageIs('/login');
+ $this->get('/')->assertRedirect('/login');
}
public function test_login()
{
- ->seePageIs('/');
}
public function test_public_viewing()
{
- $settings = app(SettingService::class);
- $settings->put('app-public', 'true');
- $this->visit('/')
- ->seePageIs('/')
- ->see('Log In');
+ $this->setSettings(['app-public' => 'true']);
+ $this->get('/')
+ ->assertOk()
+ ->assertSee('Log in');
}
public function test_registration_showing()
{
// Ensure registration form is showing
$this->setSettings(['registration-enabled' => 'true']);
- $this->visit('/login')
- ->see('Sign up')
- ->click('Sign up')
- ->seePageIs('/register');
+ $this->get('/login')
+ ->assertElementContains('a[href="' . url('/register') . '"]', 'Sign up');
}
public function test_normal_registration()
$user = factory(User::class)->make();
// Test form and ensure user is created
- $this->visit('/register')
- ->see('Sign Up')
- ->type($user->name, '#name')
- ->type($user->email, '#email')
- ->type($user->password, '#password')
- ->press('Create Account')
- ->seePageIs('/')
- ->see($user->name)
- ->seeInDatabase('users', ['name' => $user->name, 'email' => $user->email]);
+ $this->get('/register')
+ ->assertSee('Sign Up')
+ ->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]);
}
public function test_empty_registration_redirects_back_with_errors()
$this->setSettings(['registration-enabled' => 'true']);
// Test form and ensure user is created
- $this->visit('/register')
- ->press('Create Account')
- ->see('The name field is required')
- ->seePageIs('/register');
+ $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->visit('/register')
- ->type('1', '#name')
- ->type('1', '#email')
- ->type('1', '#password')
- ->press('Create Account')
- ->see('The name must be at least 2 characters.')
- ->see('The email must be a valid email address.')
- ->see('The password must be at least 8 characters.')
- ->seePageIs('/register');
+ $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->visit('/login')
- ->dontSee('Sign up');
+ $this->get('/login')->assertDontSee('Sign up');
$this->setSettings(['registration-enabled' => 'true']);
- $this->visit('/login')
- ->see('Sign up');
+ $this->get('/login')->assertSee('Sign up');
}
public function test_confirmed_registration()
$user = factory(User::class)->make();
// Go through registration process
- $this->visit('/register')
- ->see('Sign Up')
- ->type($user->name, '#name')
- ->type($user->email, '#email')
- ->type($user->password, '#password')
- ->press('Create Account')
- ->seePageIs('/register/confirm')
- ->seeInDatabase('users', ['name' => $user->name, 'email' => $user->email, 'email_confirmed' => false]);
+ $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
- $dbUser = User::where('email', '=', $user->email)->first();
+ /** @var User $dbUser */
+ $dbUser = User::query()->where('email', '=', $user->email)->first();
Notification::assertSentTo($dbUser, ConfirmEmail::class);
// Test access and resend confirmation email
- $this->login($user->email, $user->password)
- ->seePageIs('/register/confirm/awaiting')
- ->see('Resend')
- ->visit('/books')
- ->seePageIs('/register/confirm/awaiting')
- ->press('Resend Confirmation Email');
+ $resp = $this->login($user->email, $user->password);
+ $resp->assertRedirect('/register/confirm/awaiting');
+
+ $resp = $this->get('/register/confirm/awaiting');
+ $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();
});
// Check confirmation email confirmation activation.
- $this->visit('/register/confirm/' . $emailConfirmation->token)
- ->seePageIs('/')
- ->see($user->name)
- ->notSeeInDatabase('email_confirmations', ['token' => $emailConfirmation->token])
- ->seeInDatabase('users', ['name' => $dbUser->name, 'email' => $dbUser->email, 'email_confirmed' => true]);
+ $this->get('/register/confirm/' . $emailConfirmation->token)->assertRedirect('/');
+ $this->get('/')->assertSee($user->name);
+ $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 = factory(User::class)->make();
+
// Go through registration process
- $this->visit('/register')
- ->type($user->name, '#name')
- ->type($user->email, '#email')
- ->type($user->password, '#password')
- ->press('Create Account')
- ->seePageIs('/register')
- ->dontSeeInDatabase('users', ['email' => $user->email])
- ->see('That email domain does not have access to this application');
+ $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'));
- $this->visit('/register')
- ->type($user->name, '#name')
- ->type($user->email, '#email')
- ->type($user->password, '#password')
- ->press('Create Account')
- ->seePageIs('/register/confirm')
- ->seeInDatabase('users', ['name' => $user->name, 'email' => $user->email, 'email_confirmed' => false]);
-
- $this->visit('/')
- ->seePageIs('/register/confirm/awaiting');
-
- auth()->logout();
-
- $this->visit('/')->seePageIs('/login')
- ->type($user->email, '#email')
- ->type($user->password, '#password')
- ->press('Log In')
- ->seePageIs('/register/confirm/awaiting')
- ->seeText('Email Address Not Confirmed');
+ $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 = factory(User::class)->make();
+
// Go through registration process
- $this->visit('/register')
- ->type($user->name, '#name')
- ->type($user->email, '#email')
- ->type($user->password, '#password')
- ->press('Create Account')
- ->seePageIs('/register')
- ->dontSeeInDatabase('users', ['email' => $user->email])
- ->see('That email domain does not have access to this application');
+ $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');
- $this->visit('/register')
- ->type($user->name, '#name')
- ->type($user->email, '#email')
- ->type($user->password, '#password')
- ->press('Create Account')
- ->seePageIs('/register/confirm')
- ->seeInDatabase('users', ['name' => $user->name, 'email' => $user->email, 'email_confirmed' => false]);
-
- $this->visit('/')
- ->seePageIs('/register/confirm/awaiting');
-
- auth()->logout();
- $this->visit('/')->seePageIs('/login')
- ->type($user->email, '#email')
- ->type($user->password, '#password')
- ->press('Log In')
- ->seePageIs('/register/confirm/awaiting')
- ->seeText('Email Address Not Confirmed');
- }
+ $this->post('/register', $user->only('name', 'email', 'password'))
+ ->assertRedirect('/register/confirm');
+ $this->assertDatabaseHas('users', ['name' => $user->name, 'email' => $user->email, 'email_confirmed' => false]);
- public function test_user_creation()
- {
- /** @var User $user */
- $user = factory(User::class)->make();
- $adminRole = Role::getRole('admin');
-
- $this->asAdmin()
- ->visit('/settings/users')
- ->click('Add New User')
- ->type($user->name, '#name')
- ->type($user->email, '#email')
- ->check("roles[{$adminRole->id}]")
- ->type($user->password, '#password')
- ->type($user->password, '#password-confirm')
- ->press('Save')
- ->seePageIs('/settings/users')
- ->seeInDatabase('users', $user->only(['name', 'email']))
- ->see($user->name);
-
- $user->refresh();
- $this->assertStringStartsWith(Str::slug($user->name), $user->slug);
- }
+ $this->assertNull(auth()->user());
- public function test_user_updating()
- {
- $user = $this->getNormalUser();
- $password = $user->password;
- $this->asAdmin()
- ->visit('/settings/users')
- ->click($user->name)
- ->seePageIs('/settings/users/' . $user->id)
- ->see($user->email)
- ->type('Barry Scott', '#name')
- ->press('Save')
- ->seePageIs('/settings/users')
- ->seeInDatabase('users', ['id' => $user->id, 'name' => 'Barry Scott', 'password' => $password])
- ->notSeeInDatabase('users', ['name' => $user->name]);
-
- $user->refresh();
- $this->assertStringStartsWith(Str::slug($user->name), $user->slug);
+ $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_user_password_update()
+ public function test_logout()
{
- $user = $this->getNormalUser();
- $userProfilePage = '/settings/users/' . $user->id;
- $this->asAdmin()
- ->visit($userProfilePage)
- ->type('newpassword', '#password')
- ->press('Save')
- ->seePageIs($userProfilePage)
- ->see('Password confirmation required')
-
- ->type('newpassword', '#password')
- ->type('newpassword', '#password-confirm')
- ->press('Save')
- ->seePageIs('/settings/users');
-
- $userPassword = User::find($user->id)->password;
- $this->assertTrue(Hash::check('newpassword', $userPassword));
+ $this->asAdmin()->get('/')->assertOk();
+ $this->get('/logout')->assertRedirect('/');
+ $this->get('/')->assertRedirect('/login');
}
- public function test_user_deletion()
+ public function test_mfa_session_cleared_on_logout()
{
- $userDetails = factory(User::class)->make();
- $user = $this->getEditor($userDetails->toArray());
-
- $this->asAdmin()
- ->visit('/settings/users/' . $user->id)
- ->click('Delete User')
- ->see($user->name)
- ->press('Confirm')
- ->seePageIs('/settings/users')
- ->notSeeInDatabase('users', ['name' => $user->name]);
- }
+ $user = $this->getEditor();
+ $mfaSession = $this->app->make(MfaSession::class);
- public function test_user_cannot_be_deleted_if_last_admin()
- {
- $adminRole = Role::getRole('admin');
-
- // Delete all but one admin user if there are more than one
- $adminUsers = $adminRole->users;
- if (count($adminUsers) > 1) {
- foreach ($adminUsers->splice(1) as $user) {
- $user->delete();
- }
- }
-
- // Ensure we currently only have 1 admin user
- $this->assertEquals(1, $adminRole->users()->count());
- $user = $adminRole->users->first();
-
- $this->asAdmin()->visit('/settings/users/' . $user->id)
- ->click('Delete User')
- ->press('Confirm')
- ->seePageIs('/settings/users/' . $user->id)
- ->see('You cannot delete the only admin');
- }
+ $mfaSession->markVerifiedForUser($user);
+ $this->assertTrue($mfaSession->isVerifiedForUser($user));
- public function test_logout()
- {
- $this->asAdmin()
- ->visit('/')
- ->seePageIs('/')
- ->visit('/logout')
- ->visit('/')
- ->seePageIs('/login');
+ $this->asAdmin()->get('/logout');
+ $this->assertFalse($mfaSession->isVerifiedForUser($user));
}
public function test_reset_password_flow()
{
Notification::fake();
- $this->visit('/login')->click('Forgot Password?')
- ->seePageIs('/password/email')
- ->press('Send Reset Link')
- ->see('A password reset link will be sent to
[email protected] if that email address is found in the system.');
+ $this->get('/login')
+ ->assertElementContains('a[href="' . url('/password/email') . '"]', 'Forgot Password?');
- $this->seeInDatabase('password_resets', [
+ $this->get('/password/email')
+ ->assertElementContains('form[action="' . url('/password/email') . '"]', 'Send Reset Link');
+
+ $resp = $this->post('/password/email', [
+ ]);
+ $resp->assertRedirect('/password/email');
+
+ $resp = $this->get('/password/email');
+ $resp->assertSee('A password reset link will be sent to
[email protected] if that email address is found in the system.');
+
+ $this->assertDatabaseHas('password_resets', [
]);
+ /** @var User $user */
Notification::assertSentTo($user, ResetPassword::class);
$n = Notification::sent($user, ResetPassword::class);
- $this->visit('/password/reset/' . $n->first()->token)
- ->see('Reset Password')
- ->submitForm('Reset Password', [
- 'password' => 'randompass',
- 'password_confirmation' => 'randompass',
- ])->seePageIs('/')
- ->see('Your password has been successfully reset');
+ $this->get('/password/reset/' . $n->first()->token)
+ ->assertOk()
+ ->assertSee('Reset Password');
+
+ $resp = $this->post('/password/reset', [
+ '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->visit('/login')->click('Forgot Password?')
- ->seePageIs('/password/email')
- ->press('Send Reset Link')
- ->see('A password reset link will be sent to
[email protected] if that email address is found in the system.')
- ->dontSee('We can\'t find a user');
-
- $this->visit('/password/reset/arandometokenvalue')
- ->see('Reset Password')
- ->submitForm('Reset Password', [
- 'password' => 'randompass',
- 'password_confirmation' => 'randompass',
- ])->followRedirects()
- ->seePageIs('/password/reset/arandometokenvalue')
- ->dontSee('We can\'t find a user')
- ->see('The password reset token is invalid for this email address.');
+ $this->get('/password/email');
+ $resp = $this->followingRedirects()->post('/password/email', [
+ ]);
+ $resp->assertSee('A password reset link will be sent to
[email protected] 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', [
+ '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']);
- $this->visit('/password/email')
- ->seeLink('Log in')
- ->seeLink('Sign up');
+ $this->get('/password/email')
+ ->assertElementContains('a', 'Log in')
+ ->assertElementContains('a', 'Sign up');
}
public function test_login_redirects_to_initially_requested_url_correctly()
{
config()->set('app.url', 'http://localhost');
+ /** @var Page $page */
$page = Page::query()->first();
- $this->visit($page->getUrl())
- ->seePageUrlIs(url('/login'));
+ $this->get($page->getUrl())->assertRedirect(url('/login'));
- ->seePageUrlIs($page->getUrl());
+ ->assertRedirect($page->getUrl());
}
public function test_login_intended_redirect_does_not_redirect_to_external_pages()
$this->get('/login', ['referer' => 'https://example.com']);
$login = $this->post('/login', ['email' => '
[email protected]', 'password' => 'password']);
- $login->assertRedirectedTo('http://localhost');
+ $login->assertRedirect('http://localhost');
+ }
+
+ public function test_login_intended_redirect_does_not_factor_mfa_routes()
+ {
+ $this->get('/books')->assertRedirect('/login');
+ $this->get('/mfa/setup')->assertRedirect('/login');
+ $login = $this->post('/login', ['email' => '
[email protected]', 'password' => 'password']);
+ $login->assertRedirect('/books');
}
public function test_login_authenticates_admins_on_all_guards()
$this->assertFalse($log->hasWarningThatContains('Failed login for
[email protected]'));
}
+ public function test_logged_in_user_with_unconfirmed_email_is_logged_out()
+ {
+ $this->setSettings(['registration-confirmation' => 'true']);
+ $user = $this->getEditor();
+ $user->email_confirmed = false;
+ $user->save();
+
+ auth()->login($user);
+ $this->assertTrue(auth()->check());
+
+ $this->get('/books')->assertRedirect('/');
+ $this->assertFalse(auth()->check());
+ }
+
/**
* Perform a login.
*/
- protected function login(string $email, string $password): AuthTest
+ protected function login(string $email, string $password): TestResponse
{
- return $this->visit('/login')
- ->type($email, '#email')
- ->type($password, '#password')
- ->press('Log In');
+ return $this->post('/login', compact('email', 'password'));
}
}
'services.ldap.remove_from_groups' => true,
]);
- $this->commonLdapMocks(1, 1, 3, 4, 3, 2);
+ $this->commonLdapMocks(1, 1, 6, 8, 6, 4);
$this->mockLdap->shouldReceive('searchAndGetEntries')
- ->times(3)
+ ->times(6)
->andReturn(['count' => 1, 0 => [
'uid' => [$user->name],
'cn' => [$user->name],
],
]]);
- $this->followingRedirects()->mockUserLogin()->assertSee('Thanks for registering!');
+ $login = $this->followingRedirects()->mockUserLogin();
+ $login->assertSee('Thanks for registering!');
$this->assertDatabaseHas('users', [
'email' => $user->email,
'email_confirmed' => false,
'role_id' => $roleToReceive->id,
]);
+ $this->assertNull(auth()->user());
+
$homePage = $this->get('/');
- $homePage->assertRedirect('/register/confirm/awaiting');
+ $homePage->assertRedirect('/login');
+
+ $login = $this->followingRedirects()->mockUserLogin();
+ $login->assertSee('Email Address Not Confirmed');
}
public function test_failed_logins_are_logged_when_message_configured()
--- /dev/null
+<?php
+
+namespace Tests\Auth;
+
+use BookStack\Actions\ActivityType;
+use BookStack\Auth\Access\Mfa\MfaValue;
+use BookStack\Auth\User;
+use PragmaRX\Google2FA\Google2FA;
+use Tests\TestCase;
+
+class MfaConfigurationTest extends TestCase
+{
+ public function test_totp_setup()
+ {
+ $editor = $this->getEditor();
+ $this->assertDatabaseMissing('mfa_values', ['user_id' => $editor->id]);
+
+ // Setup page state
+ $resp = $this->actingAs($editor)->get('/mfa/setup');
+ $resp->assertElementContains('a[href$="/mfa/totp/generate"]', 'Setup');
+
+ // Generate page access
+ $resp = $this->get('/mfa/totp/generate');
+ $resp->assertSee('Mobile App Setup');
+ $resp->assertSee('Verify Setup');
+ $resp->assertElementExists('form[action$="/mfa/totp/confirm"] button');
+ $this->assertSessionHas('mfa-setup-totp-secret');
+ $svg = $resp->getElementHtml('#main-content .card svg');
+
+ // Validation error, code should remain the same
+ $resp = $this->post('/mfa/totp/confirm', [
+ 'code' => 'abc123',
+ ]);
+ $resp->assertRedirect('/mfa/totp/generate');
+ $resp = $this->followRedirects($resp);
+ $resp->assertSee('The provided code is not valid or has expired.');
+ $revisitSvg = $resp->getElementHtml('#main-content .card svg');
+ $this->assertTrue($svg === $revisitSvg);
+ $secret = decrypt(session()->get('mfa-setup-totp-secret'));
+
+ $resp->assertSee(htmlentities("?secret={$secret}&issuer=BookStack&algorithm=SHA1&digits=6&period=30"));
+
+ // Successful confirmation
+ $google2fa = new Google2FA();
+ $otp = $google2fa->getCurrentOtp($secret);
+ $resp = $this->post('/mfa/totp/confirm', [
+ 'code' => $otp,
+ ]);
+ $resp->assertRedirect('/mfa/setup');
+
+ // Confirmation of setup
+ $resp = $this->followRedirects($resp);
+ $resp->assertSee('Multi-factor method successfully configured');
+ $resp->assertElementContains('a[href$="/mfa/totp/generate"]', 'Reconfigure');
+
+ $this->assertDatabaseHas('mfa_values', [
+ 'user_id' => $editor->id,
+ 'method' => 'totp',
+ ]);
+ $this->assertFalse(session()->has('mfa-setup-totp-secret'));
+ $value = MfaValue::query()->where('user_id', '=', $editor->id)
+ ->where('method', '=', 'totp')->first();
+ $this->assertEquals($secret, decrypt($value->value));
+ }
+
+ public function test_backup_codes_setup()
+ {
+ $editor = $this->getEditor();
+ $this->assertDatabaseMissing('mfa_values', ['user_id' => $editor->id]);
+
+ // Setup page state
+ $resp = $this->actingAs($editor)->get('/mfa/setup');
+ $resp->assertElementContains('a[href$="/mfa/backup_codes/generate"]', 'Setup');
+
+ // Generate page access
+ $resp = $this->get('/mfa/backup_codes/generate');
+ $resp->assertSee('Backup Codes');
+ $resp->assertElementContains('form[action$="/mfa/backup_codes/confirm"]', 'Confirm and Enable');
+ $this->assertSessionHas('mfa-setup-backup-codes');
+ $codes = decrypt(session()->get('mfa-setup-backup-codes'));
+ // Check code format
+ $this->assertCount(16, $codes);
+ $this->assertEquals(16 * 11, strlen(implode('', $codes)));
+ // Check download link
+ $resp->assertSee(base64_encode(implode("\n\n", $codes)));
+
+ // Confirm submit
+ $resp = $this->post('/mfa/backup_codes/confirm');
+ $resp->assertRedirect('/mfa/setup');
+
+ // Confirmation of setup
+ $resp = $this->followRedirects($resp);
+ $resp->assertSee('Multi-factor method successfully configured');
+ $resp->assertElementContains('a[href$="/mfa/backup_codes/generate"]', 'Reconfigure');
+
+ $this->assertDatabaseHas('mfa_values', [
+ 'user_id' => $editor->id,
+ 'method' => 'backup_codes',
+ ]);
+ $this->assertFalse(session()->has('mfa-setup-backup-codes'));
+ $value = MfaValue::query()->where('user_id', '=', $editor->id)
+ ->where('method', '=', 'backup_codes')->first();
+ $this->assertEquals($codes, json_decode(decrypt($value->value)));
+ }
+
+ public function test_backup_codes_cannot_be_confirmed_if_not_previously_generated()
+ {
+ $resp = $this->asEditor()->post('/mfa/backup_codes/confirm');
+ $resp->assertStatus(500);
+ }
+
+ public function test_mfa_method_count_is_visible_on_user_edit_page()
+ {
+ $user = $this->getEditor();
+ $resp = $this->actingAs($this->getAdmin())->get($user->getEditUrl());
+ $resp->assertSee('0 methods configured');
+
+ MfaValue::upsertWithValue($user, MfaValue::METHOD_TOTP, 'test');
+ $resp = $this->get($user->getEditUrl());
+ $resp->assertSee('1 method configured');
+
+ MfaValue::upsertWithValue($user, MfaValue::METHOD_BACKUP_CODES, 'test');
+ $resp = $this->get($user->getEditUrl());
+ $resp->assertSee('2 methods configured');
+ }
+
+ public function test_mfa_setup_link_only_shown_when_viewing_own_user_edit_page()
+ {
+ $admin = $this->getAdmin();
+ $resp = $this->actingAs($admin)->get($admin->getEditUrl());
+ $resp->assertElementExists('a[href$="/mfa/setup"]');
+
+ $resp = $this->actingAs($admin)->get($this->getEditor()->getEditUrl());
+ $resp->assertElementNotExists('a[href$="/mfa/setup"]');
+ }
+
+ public function test_mfa_indicator_shows_in_user_list()
+ {
+ $admin = $this->getAdmin();
+ User::query()->where('id', '!=', $admin->id)->delete();
+
+ $resp = $this->actingAs($admin)->get('/settings/users');
+ $resp->assertElementNotExists('[title="MFA Configured"] svg');
+
+ MfaValue::upsertWithValue($admin, MfaValue::METHOD_TOTP, 'test');
+ $resp = $this->actingAs($admin)->get('/settings/users');
+ $resp->assertElementExists('[title="MFA Configured"] svg');
+ }
+
+ public function test_remove_mfa_method()
+ {
+ $admin = $this->getAdmin();
+
+ MfaValue::upsertWithValue($admin, MfaValue::METHOD_TOTP, 'test');
+ $this->assertEquals(1, $admin->mfaValues()->count());
+ $resp = $this->actingAs($admin)->get('/mfa/setup');
+ $resp->assertElementExists('form[action$="/mfa/totp/remove"]');
+
+ $resp = $this->delete('/mfa/totp/remove');
+ $resp->assertRedirect('/mfa/setup');
+ $resp = $this->followRedirects($resp);
+ $resp->assertSee('Multi-factor method successfully removed');
+
+ $this->assertActivityExists(ActivityType::MFA_REMOVE_METHOD);
+ $this->assertEquals(0, $admin->mfaValues()->count());
+ }
+}
--- /dev/null
+<?php
+
+namespace Tests\Auth;
+
+use BookStack\Auth\Access\LoginService;
+use BookStack\Auth\Access\Mfa\MfaValue;
+use BookStack\Auth\Access\Mfa\TotpService;
+use BookStack\Auth\Role;
+use BookStack\Auth\User;
+use BookStack\Exceptions\StoppedAuthenticationException;
+use Illuminate\Support\Facades\Hash;
+use PragmaRX\Google2FA\Google2FA;
+use Tests\TestCase;
+use Tests\TestResponse;
+
+class MfaVerificationTest extends TestCase
+{
+ public function test_totp_verification()
+ {
+ [$user, $secret, $loginResp] = $this->startTotpLogin();
+ $loginResp->assertRedirect('/mfa/verify');
+
+ $resp = $this->get('/mfa/verify');
+ $resp->assertSee('Verify Access');
+ $resp->assertSee('Enter the code, generated using your mobile app, below:');
+ $resp->assertElementExists('form[action$="/mfa/totp/verify"] input[name="code"]');
+
+ $google2fa = new Google2FA();
+ $resp = $this->post('/mfa/totp/verify', [
+ 'code' => $google2fa->getCurrentOtp($secret),
+ ]);
+ $resp->assertRedirect('/');
+ $this->assertEquals($user->id, auth()->user()->id);
+ }
+
+ public function test_totp_verification_fails_on_missing_invalid_code()
+ {
+ [$user, $secret, $loginResp] = $this->startTotpLogin();
+
+ $resp = $this->get('/mfa/verify');
+ $resp = $this->post('/mfa/totp/verify', [
+ 'code' => '',
+ ]);
+ $resp->assertRedirect('/mfa/verify');
+
+ $resp = $this->get('/mfa/verify');
+ $resp->assertSeeText('The code field is required.');
+ $this->assertNull(auth()->user());
+
+ $resp = $this->post('/mfa/totp/verify', [
+ 'code' => '123321',
+ ]);
+ $resp->assertRedirect('/mfa/verify');
+ $resp = $this->get('/mfa/verify');
+
+ $resp->assertSeeText('The provided code is not valid or has expired.');
+ $this->assertNull(auth()->user());
+ }
+
+ public function test_backup_code_verification()
+ {
+ [$user, $codes, $loginResp] = $this->startBackupCodeLogin();
+ $loginResp->assertRedirect('/mfa/verify');
+
+ $resp = $this->get('/mfa/verify');
+ $resp->assertSee('Verify Access');
+ $resp->assertSee('Backup Code');
+ $resp->assertSee('Enter one of your remaining backup codes below:');
+ $resp->assertElementExists('form[action$="/mfa/backup_codes/verify"] input[name="code"]');
+
+ $resp = $this->post('/mfa/backup_codes/verify', [
+ 'code' => $codes[1],
+ ]);
+
+ $resp->assertRedirect('/');
+ $this->assertEquals($user->id, auth()->user()->id);
+ // Ensure code no longer exists in available set
+ $userCodes = MfaValue::getValueForUser($user, MfaValue::METHOD_BACKUP_CODES);
+ $this->assertStringNotContainsString($codes[1], $userCodes);
+ $this->assertStringContainsString($codes[0], $userCodes);
+ }
+
+ public function test_backup_code_verification_fails_on_missing_or_invalid_code()
+ {
+ [$user, $codes, $loginResp] = $this->startBackupCodeLogin();
+
+ $resp = $this->get('/mfa/verify');
+ $resp = $this->post('/mfa/backup_codes/verify', [
+ 'code' => '',
+ ]);
+ $resp->assertRedirect('/mfa/verify');
+
+ $resp = $this->get('/mfa/verify');
+ $resp->assertSeeText('The code field is required.');
+ $this->assertNull(auth()->user());
+
+ $resp = $this->post('/mfa/backup_codes/verify', [
+ 'code' => 'ab123-ab456',
+ ]);
+ $resp->assertRedirect('/mfa/verify');
+
+ $resp = $this->get('/mfa/verify');
+ $resp->assertSeeText('The provided code is not valid or has already been used.');
+ $this->assertNull(auth()->user());
+ }
+
+ public function test_backup_code_verification_fails_on_attempted_code_reuse()
+ {
+ [$user, $codes, $loginResp] = $this->startBackupCodeLogin();
+
+ $this->post('/mfa/backup_codes/verify', [
+ 'code' => $codes[0],
+ ]);
+ $this->assertNotNull(auth()->user());
+ auth()->logout();
+ session()->flush();
+
+ $this->post('/login', ['email' => $user->email, 'password' => 'password']);
+ $this->get('/mfa/verify');
+ $resp = $this->post('/mfa/backup_codes/verify', [
+ 'code' => $codes[0],
+ ]);
+ $resp->assertRedirect('/mfa/verify');
+ $this->assertNull(auth()->user());
+
+ $resp = $this->get('/mfa/verify');
+ $resp->assertSeeText('The provided code is not valid or has already been used.');
+ }
+
+ public function test_backup_code_verification_shows_warning_when_limited_codes_remain()
+ {
+ [$user, $codes, $loginResp] = $this->startBackupCodeLogin(['abc12-def45', 'abc12-def46']);
+
+ $resp = $this->post('/mfa/backup_codes/verify', [
+ 'code' => $codes[0],
+ ]);
+ $resp = $this->followRedirects($resp);
+ $resp->assertSeeText('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.');
+ }
+
+ public function test_both_mfa_options_available_if_set_on_profile()
+ {
+ $user = $this->getEditor();
+ $user->password = Hash::make('password');
+ $user->save();
+
+ MfaValue::upsertWithValue($user, MfaValue::METHOD_TOTP, 'abc123');
+ MfaValue::upsertWithValue($user, MfaValue::METHOD_BACKUP_CODES, '["abc12-def456"]');
+
+ /** @var TestResponse $mfaView */
+ $mfaView = $this->followingRedirects()->post('/login', [
+ 'email' => $user->email,
+ 'password' => 'password',
+ ]);
+
+ // Totp shown by default
+ $mfaView->assertElementExists('form[action$="/mfa/totp/verify"] input[name="code"]');
+ $mfaView->assertElementContains('a[href$="/mfa/verify?method=backup_codes"]', 'Verify using a backup code');
+
+ // Ensure can view backup_codes view
+ $resp = $this->get('/mfa/verify?method=backup_codes');
+ $resp->assertElementExists('form[action$="/mfa/backup_codes/verify"] input[name="code"]');
+ $resp->assertElementContains('a[href$="/mfa/verify?method=totp"]', 'Verify using a mobile app');
+ }
+
+ public function test_mfa_required_with_no_methods_leads_to_setup()
+ {
+ $user = $this->getEditor();
+ $user->password = Hash::make('password');
+ $user->save();
+ /** @var Role $role */
+ $role = $user->roles->first();
+ $role->mfa_enforced = true;
+ $role->save();
+
+ $this->assertDatabaseMissing('mfa_values', [
+ 'user_id' => $user->id,
+ ]);
+
+ /** @var TestResponse $resp */
+ $resp = $this->followingRedirects()->post('/login', [
+ 'email' => $user->email,
+ 'password' => 'password',
+ ]);
+
+ $resp->assertSeeText('No Methods Configured');
+ $resp->assertElementContains('a[href$="/mfa/setup"]', 'Configure');
+
+ $this->get('/mfa/backup_codes/generate');
+ $resp = $this->post('/mfa/backup_codes/confirm');
+ $resp->assertRedirect('/login');
+ $this->assertDatabaseHas('mfa_values', [
+ 'user_id' => $user->id,
+ ]);
+
+ $resp = $this->get('/login');
+ $resp->assertSeeText('Multi-factor method configured, Please now login again using the configured method.');
+
+ $resp = $this->followingRedirects()->post('/login', [
+ 'email' => $user->email,
+ 'password' => 'password',
+ ]);
+ $resp->assertSeeText('Enter one of your remaining backup codes below:');
+ }
+
+ public function test_mfa_setup_route_access()
+ {
+ $routes = [
+ ['get', '/mfa/setup'],
+ ['get', '/mfa/totp/generate'],
+ ['post', '/mfa/totp/confirm'],
+ ['get', '/mfa/backup_codes/generate'],
+ ['post', '/mfa/backup_codes/confirm'],
+ ];
+
+ // Non-auth access
+ foreach ($routes as [$method, $path]) {
+ $resp = $this->call($method, $path);
+ $resp->assertRedirect('/login');
+ }
+
+ // Attempted login user, who has configured mfa, access
+ // Sets up user that has MFA required after attempted login.
+ $loginService = $this->app->make(LoginService::class);
+ $user = $this->getEditor();
+ /** @var Role $role */
+ $role = $user->roles->first();
+ $role->mfa_enforced = true;
+ $role->save();
+
+ try {
+ $loginService->login($user, 'testing');
+ } catch (StoppedAuthenticationException $e) {
+ }
+ $this->assertNotNull($loginService->getLastLoginAttemptUser());
+
+ MfaValue::upsertWithValue($user, MfaValue::METHOD_BACKUP_CODES, '[]');
+ foreach ($routes as [$method, $path]) {
+ $resp = $this->call($method, $path);
+ $resp->assertRedirect('/login');
+ }
+ }
+
+ /**
+ * @return Array<User, string, TestResponse>
+ */
+ protected function startTotpLogin(): array
+ {
+ $secret = $this->app->make(TotpService::class)->generateSecret();
+ $user = $this->getEditor();
+ $user->password = Hash::make('password');
+ $user->save();
+ MfaValue::upsertWithValue($user, MfaValue::METHOD_TOTP, $secret);
+ $loginResp = $this->post('/login', [
+ 'email' => $user->email,
+ 'password' => 'password',
+ ]);
+
+ return [$user, $secret, $loginResp];
+ }
+
+ /**
+ * @return Array<User, string, TestResponse>
+ */
+ protected function startBackupCodeLogin($codes = ['kzzu6-1pgll', 'bzxnf-plygd', 'bwdsp-ysl51', '1vo93-ioy7n', 'lf7nw-wdyka', 'xmtrd-oplac']): array
+ {
+ $user = $this->getEditor();
+ $user->password = Hash::make('password');
+ $user->save();
+ MfaValue::upsertWithValue($user, MfaValue::METHOD_BACKUP_CODES, json_encode($codes));
+ $loginResp = $this->post('/login', [
+ 'email' => $user->email,
+ 'password' => 'password',
+ ]);
+
+ return [$user, $codes, $loginResp];
+ }
+}
$this->assertEquals('http://localhost/register/confirm', url()->current());
$acsPost->assertSee('Please check your email and click the confirmation button to access BookStack.');
+ /** @var User $user */
$user = User::query()->where('external_auth_id', '=', 'user')->first();
$userRoleIds = $user->roles()->pluck('id');
$this->assertContains($memberRole->id, $userRoleIds, 'User was assigned to member role');
$this->assertContains($adminRole->id, $userRoleIds, 'User was assigned to admin role');
- $this->assertTrue($user->email_confirmed == false, 'User email remains unconfirmed');
+ $this->assertFalse(boolval($user->email_confirmed), 'User email remains unconfirmed');
});
+ $this->assertNull(auth()->user());
$homeGet = $this->get('/');
- $homeGet->assertRedirect('/register/confirm/awaiting');
+ $homeGet->assertRedirect('/login');
}
public function test_login_where_existing_non_saml_user_shows_warning()
namespace Tests\Auth;
+use BookStack\Actions\ActivityType;
use BookStack\Auth\SocialAccount;
use BookStack\Auth\User;
-use DB;
+use Illuminate\Support\Facades\DB;
use Laravel\Socialite\Contracts\Factory;
use Laravel\Socialite\Contracts\Provider;
use Mockery;
]);
$resp = $this->followingRedirects()->get('/login/service/github/callback');
$resp->assertDontSee('login-form');
+ $this->assertActivityExists(ActivityType::AUTH_LOGIN, null, 'github; (' . $this->getAdmin()->id . ') ' . $this->getAdmin()->name);
}
public function test_social_account_detach()
use BookStack\Auth\User;
use BookStack\Notifications\UserInvite;
use Carbon\Carbon;
-use DB;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Str;
-use Notification;
use Tests\TestCase;
class UserInviteTest extends TestCase
+++ /dev/null
-<?php
-
-namespace Tests;
-
-use BookStack\Auth\Permissions\PermissionService;
-use BookStack\Auth\User;
-use BookStack\Entities\Models\Book;
-use BookStack\Entities\Models\Chapter;
-use BookStack\Entities\Models\Entity;
-use BookStack\Entities\Models\Page;
-use BookStack\Settings\SettingService;
-use DB;
-use Illuminate\Contracts\Console\Kernel;
-use Illuminate\Foundation\Application;
-use Illuminate\Foundation\Testing\DatabaseTransactions;
-use Laravel\BrowserKitTesting\TestCase;
-use Symfony\Component\DomCrawler\Crawler;
-
-abstract class BrowserKitTest extends TestCase
-{
- use DatabaseTransactions;
- use SharedTestHelpers;
-
- /**
- * The base URL to use while testing the application.
- *
- * @var string
- */
- protected $baseUrl = 'http://localhost';
-
- public function tearDown(): void
- {
- DB::disconnect();
- parent::tearDown();
- }
-
- /**
- * Creates the application.
- *
- * @return Application
- */
- public function createApplication()
- {
- $app = require __DIR__ . '/../bootstrap/app.php';
-
- $app->make(Kernel::class)->bootstrap();
-
- return $app;
- }
-
- /**
- * Quickly sets an array of settings.
- *
- * @param $settingsArray
- */
- protected function setSettings($settingsArray)
- {
- $settings = app(SettingService::class);
- foreach ($settingsArray as $key => $value) {
- $settings->put($key, $value);
- }
- }
-
- /**
- * Create a group of entities that belong to a specific user.
- */
- 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 = factory(Book::class)->create($userAttrs);
- $chapter = factory(Chapter::class)->create(array_merge(['book_id' => $book->id], $userAttrs));
- $page = factory(Page::class)->create(array_merge(['book_id' => $book->id, 'chapter_id' => $chapter->id], $userAttrs));
- $restrictionService = $this->app[PermissionService::class];
- $restrictionService->buildJointPermissionsForEntity($book);
-
- return compact('book', 'chapter', 'page');
- }
-
- /**
- * Helper for updating entity permissions.
- *
- * @param Entity $entity
- */
- protected function updateEntityPermissions(Entity $entity)
- {
- $restrictionService = $this->app[PermissionService::class];
- $restrictionService->buildJointPermissionsForEntity($entity);
- }
-
- /**
- * Quick way to create a new user without any permissions.
- *
- * @param array $attributes
- *
- * @return mixed
- */
- protected function getNewBlankUser($attributes = [])
- {
- $user = factory(User::class)->create($attributes);
-
- return $user;
- }
-
- /**
- * Assert that a given string is seen inside an element.
- *
- * @param bool|string|null $element
- * @param int $position
- * @param string $text
- * @param bool $negate
- *
- * @return $this
- */
- protected function seeInNthElement($element, $position, $text, $negate = false)
- {
- $method = $negate ? 'assertDoesNotMatchRegularExpression' : 'assertMatchesRegularExpression';
-
- $rawPattern = preg_quote($text, '/');
-
- $escapedPattern = preg_quote(e($text), '/');
-
- $content = $this->crawler->filter($element)->eq($position)->html();
-
- $pattern = $rawPattern == $escapedPattern
- ? $rawPattern : "({$rawPattern}|{$escapedPattern})";
-
- $this->$method("/$pattern/i", $content);
-
- return $this;
- }
-
- /**
- * Assert that the current page matches a given URI.
- *
- * @param string $uri
- *
- * @return $this
- */
- protected function seePageUrlIs($uri)
- {
- $this->assertEquals(
- $uri,
- $this->currentUri,
- "Did not land on expected page [{$uri}].\n"
- );
-
- return $this;
- }
-
- /**
- * Do a forced visit that does not error out on exception.
- *
- * @param string $uri
- * @param array $parameters
- * @param array $cookies
- * @param array $files
- *
- * @return $this
- */
- protected function forceVisit($uri, $parameters = [], $cookies = [], $files = [])
- {
- $method = 'GET';
- $uri = $this->prepareUrlForRequest($uri);
- $this->call($method, $uri, $parameters, $cookies, $files);
- $this->clearInputs()->followRedirects();
- $this->currentUri = $this->app->make('request')->fullUrl();
- $this->crawler = new Crawler($this->response->getContent(), $uri);
-
- return $this;
- }
-
- /**
- * Click the text within the selected element.
- *
- * @param $parentElement
- * @param $linkText
- *
- * @return $this
- */
- protected function clickInElement($parentElement, $linkText)
- {
- $elem = $this->crawler->filter($parentElement);
- $link = $elem->selectLink($linkText);
- $this->visit($link->link()->getUri());
-
- return $this;
- }
-
- /**
- * Check if the page contains the given element.
- *
- * @param string $selector
- */
- protected function pageHasElement($selector)
- {
- $elements = $this->crawler->filter($selector);
- $this->assertTrue(count($elements) > 0, 'The page does not contain an element matching ' . $selector);
-
- return $this;
- }
-
- /**
- * Check if the page contains the given element.
- *
- * @param string $selector
- */
- protected function pageNotHasElement($selector)
- {
- $elements = $this->crawler->filter($selector);
- $this->assertFalse(count($elements) > 0, 'The page contains ' . count($elements) . ' elements matching ' . $selector);
-
- return $this;
- }
-}
--- /dev/null
+<?php
+
+namespace Tests\Commands;
+
+use BookStack\Auth\Access\Mfa\MfaValue;
+use BookStack\Auth\User;
+use Tests\TestCase;
+
+class ResetMfaCommandTest extends TestCase
+{
+ public function test_command_requires_email_or_id_option()
+ {
+ $this->artisan('bookstack:reset-mfa')
+ ->expectsOutput('Either a --id=<number> or --email=<email> option must be provided.')
+ ->assertExitCode(1);
+ }
+
+ public function test_command_runs_with_provided_email()
+ {
+ /** @var User $user */
+ $user = User::query()->first();
+ MfaValue::upsertWithValue($user, MfaValue::METHOD_TOTP, 'test');
+
+ $this->assertEquals(1, $user->mfaValues()->count());
+ $this->artisan("bookstack:reset-mfa --email={$user->email}")
+ ->expectsQuestion('Are you sure you want to proceed?', true)
+ ->expectsOutput('User MFA methods have been reset.')
+ ->assertExitCode(0);
+ $this->assertEquals(0, $user->mfaValues()->count());
+ }
+
+ public function test_command_runs_with_provided_id()
+ {
+ /** @var User $user */
+ $user = User::query()->first();
+ MfaValue::upsertWithValue($user, MfaValue::METHOD_TOTP, 'test');
+
+ $this->assertEquals(1, $user->mfaValues()->count());
+ $this->artisan("bookstack:reset-mfa --id={$user->id}")
+ ->expectsQuestion('Are you sure you want to proceed?', true)
+ ->expectsOutput('User MFA methods have been reset.')
+ ->assertExitCode(0);
+ $this->assertEquals(0, $user->mfaValues()->count());
+ }
+
+ public function test_saying_no_to_confirmation_does_not_reset_mfa()
+ {
+ /** @var User $user */
+ $user = User::query()->first();
+ MfaValue::upsertWithValue($user, MfaValue::METHOD_TOTP, 'test');
+
+ $this->assertEquals(1, $user->mfaValues()->count());
+ $this->artisan("bookstack:reset-mfa --id={$user->id}")
+ ->expectsQuestion('Are you sure you want to proceed?', false)
+ ->assertExitCode(1);
+ $this->assertEquals(1, $user->mfaValues()->count());
+ }
+
+ public function test_giving_non_existing_user_shows_error_message()
+ {
+ ->assertExitCode(1);
+ }
+}
$this->assertDatabaseHas('entity_permissions', ['restrictable_id' => $child->id, 'action' => 'update', 'role_id' => $editorRole->id]);
}
+ public function test_permission_page_has_a_warning_about_no_cascading()
+ {
+ $shelf = Bookshelf::first();
+ $resp = $this->asAdmin()->get($shelf->getUrl('/permissions'));
+ $resp->assertSeeText('Permissions on bookshelves do not automatically cascade to contained books.');
+ }
+
public function test_bookshelves_show_in_breadcrumbs_if_in_context()
{
$shelf = Bookshelf::first();
$resp = $this->asEditor()->get($newBook->getUrl());
$resp->assertDontSee($shelfInfo['name']);
}
+
+ public function test_cancel_on_child_book_creation_returns_to_original_shelf()
+ {
+ /** @var Bookshelf $shelf */
+ $shelf = Bookshelf::query()->first();
+ $resp = $this->asEditor()->get($shelf->getUrl('/create-book'));
+ $resp->assertElementContains('form a[href="' . $shelf->getUrl() . '"]', 'Cancel');
+ }
}
class BookTest extends TestCase
{
- public function test_book_delete()
+ public function test_create()
+ {
+ $book = factory(Book::class)->make([
+ 'name' => 'My First Book',
+ ]);
+
+ $resp = $this->asEditor()->get('/books');
+ $resp->assertElementContains('a[href="' . url('/create-book') . '"]', 'Create New Book');
+
+ $resp = $this->get('/create-book');
+ $resp->assertElementContains('form[action="' . url('/books') . '"][method="POST"]', 'Save Book');
+
+ $resp = $this->post('/books', $book->only('name', 'description'));
+ $resp->assertRedirect('/books/my-first-book');
+
+ $resp = $this->get('/books/my-first-book');
+ $resp->assertSee($book->name);
+ $resp->assertSee($book->description);
+ }
+
+ public function test_create_uses_different_slugs_when_name_reused()
+ {
+ $book = factory(Book::class)->make([
+ 'name' => 'My First Book',
+ ]);
+
+ $this->asEditor()->post('/books', $book->only('name', 'description'));
+ $this->asEditor()->post('/books', $book->only('name', 'description'));
+
+ $books = Book::query()->where('name', '=', $book->name)
+ ->orderBy('id', 'desc')
+ ->take(2)
+ ->get();
+
+ $this->assertMatchesRegularExpression('/my-first-book-[0-9a-zA-Z]{3}/', $books[0]->slug);
+ $this->assertEquals('my-first-book', $books[1]->slug);
+ }
+
+ public function test_update()
+ {
+ /** @var Book $book */
+ $book = Book::query()->first();
+ // Cheeky initial update to refresh slug
+ $this->asEditor()->put($book->getUrl(), ['name' => $book->name . '5', 'description' => $book->description]);
+ $book->refresh();
+
+ $newName = $book->name . ' Updated';
+ $newDesc = $book->description . ' with more content';
+
+ $resp = $this->get($book->getUrl('/edit'));
+ $resp->assertSee($book->name);
+ $resp->assertSee($book->description);
+ $resp->assertElementContains('form[action="' . $book->getUrl() . '"]', 'Save Book');
+
+ $resp = $this->put($book->getUrl(), ['name' => $newName, 'description' => $newDesc]);
+ $resp->assertRedirect($book->getUrl() . '-updated');
+
+ $resp = $this->get($book->getUrl() . '-updated');
+ $resp->assertSee($newName);
+ $resp->assertSee($newDesc);
+ }
+
+ public function test_delete()
{
$book = Book::query()->whereHas('pages')->whereHas('chapters')->first();
$this->assertNull($book->deleted_at);
$redirectReq->assertNotificationContains('Book Successfully Deleted');
}
+ public function test_cancel_on_create_page_leads_back_to_books_listing()
+ {
+ $resp = $this->asEditor()->get('/create-book');
+ $resp->assertElementContains('form a[href="' . url('/books') . '"]', 'Cancel');
+ }
+
+ public function test_cancel_on_edit_book_page_leads_back_to_book()
+ {
+ /** @var Book $book */
+ $book = Book::query()->first();
+ $resp = $this->asEditor()->get($book->getUrl('/edit'));
+ $resp->assertElementContains('form a[href="' . $book->getUrl() . '"]', 'Cancel');
+ }
+
public function test_next_previous_navigation_controls_show_within_book_content()
{
$book = Book::query()->first();
$resp->assertElementContains('#sibling-navigation', 'Previous');
$resp->assertElementContains('#sibling-navigation', substr($chapter->name, 0, 20));
}
+
+ public function test_recently_viewed_books_updates_as_expected()
+ {
+ $books = Book::all()->take(2);
+
+ $this->asAdmin()->get('/books')
+ ->assertElementNotContains('#recents', $books[0]->name)
+ ->assertElementNotContains('#recents', $books[1]->name);
+
+ $this->get($books[0]->getUrl());
+ $this->get($books[1]->getUrl());
+
+ $this->get('/books')
+ ->assertElementContains('#recents', $books[0]->name)
+ ->assertElementContains('#recents', $books[1]->name);
+ }
+
+ public function test_popular_books_updates_upon_visits()
+ {
+ $books = Book::all()->take(2);
+
+ $this->asAdmin()->get('/books')
+ ->assertElementNotContains('#popular', $books[0]->name)
+ ->assertElementNotContains('#popular', $books[1]->name);
+
+ $this->get($books[0]->getUrl());
+ $this->get($books[1]->getUrl());
+ $this->get($books[0]->getUrl());
+
+ $this->get('/books')
+ ->assertElementContains('#popular .book:nth-child(1)', $books[0]->name)
+ ->assertElementContains('#popular .book:nth-child(2)', $books[1]->name);
+ }
+
+ public function test_books_view_shows_view_toggle_option()
+ {
+ /** @var Book $book */
+ $editor = $this->getEditor();
+ setting()->putUser($editor, 'books_view_type', 'list');
+
+ $resp = $this->actingAs($editor)->get('/books');
+ $resp->assertElementContains('form[action$="/settings/users/' . $editor->id . '/switch-books-view"]', 'Grid View');
+ $resp->assertElementExists('input[name="view_type"][value="grid"]');
+
+ $resp = $this->patch("/settings/users/{$editor->id}/switch-books-view", ['view_type' => 'grid']);
+ $resp->assertRedirect();
+ $this->assertEquals('grid', setting()->getUser($editor, 'books_view_type'));
+
+ $resp = $this->actingAs($editor)->get('/books');
+ $resp->assertElementContains('form[action$="/settings/users/' . $editor->id . '/switch-books-view"]', 'List View');
+ $resp->assertElementExists('input[name="view_type"][value="list"]');
+
+ $resp = $this->patch("/settings/users/{$editor->id}/switch-books-view", ['view_type' => 'list']);
+ $resp->assertRedirect();
+ $this->assertEquals('list', setting()->getUser($editor, 'books_view_type'));
+ }
+
+ public function test_slug_multi_byte_url_safe()
+ {
+ $book = $this->newBook([
+ 'name' => 'информация',
+ ]);
+
+ $this->assertEquals('informatsiya', $book->slug);
+
+ $book = $this->newBook([
+ 'name' => '¿Qué?',
+ ]);
+
+ $this->assertEquals('que', $book->slug);
+ }
+
+ public function test_slug_format()
+ {
+ $book = $this->newBook([
+ 'name' => 'PartA / PartB / PartC',
+ ]);
+
+ $this->assertEquals('parta-partb-partc', $book->slug);
+ }
}
namespace Tests\Entity;
+use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Chapter;
use Tests\TestCase;
class ChapterTest extends TestCase
{
- public function test_chapter_delete()
+ public function test_create()
+ {
+ /** @var Book $book */
+ $book = Book::query()->first();
+
+ $chapter = factory(Chapter::class)->make([
+ 'name' => 'My First Chapter',
+ ]);
+
+ $resp = $this->asEditor()->get($book->getUrl());
+ $resp->assertElementContains('a[href="' . $book->getUrl('/create-chapter') . '"]', 'New Chapter');
+
+ $resp = $this->get($book->getUrl('/create-chapter'));
+ $resp->assertElementContains('form[action="' . $book->getUrl('/create-chapter') . '"][method="POST"]', 'Save Chapter');
+
+ $resp = $this->post($book->getUrl('/create-chapter'), $chapter->only('name', 'description'));
+ $resp->assertRedirect($book->getUrl('/chapter/my-first-chapter'));
+
+ $resp = $this->get($book->getUrl('/chapter/my-first-chapter'));
+ $resp->assertSee($chapter->name);
+ $resp->assertSee($chapter->description);
+ }
+
+ public function test_delete()
{
$chapter = Chapter::query()->whereHas('pages')->first();
$this->assertNull($chapter->deleted_at);
--- /dev/null
+<?php
+
+namespace Tests\Entity;
+
+use BookStack\Auth\UserRepo;
+use BookStack\Entities\Models\Entity;
+use BookStack\Entities\Repos\PageRepo;
+use Tests\TestCase;
+
+class EntityAccessTest extends TestCase
+{
+ public function test_entities_viewable_after_creator_deletion()
+ {
+ // Create required assets and revisions
+ $creator = $this->getEditor();
+ $updater = $this->getViewer();
+ $entities = $this->createEntityChainBelongingToUser($creator, $updater);
+ app()->make(UserRepo::class)->destroy($creator);
+ app()->make(PageRepo::class)->update($entities['page'], ['html' => '<p>hello!</p>>']);
+
+ $this->checkEntitiesViewable($entities);
+ }
+
+ public function test_entities_viewable_after_updater_deletion()
+ {
+ // Create required assets and revisions
+ $creator = $this->getViewer();
+ $updater = $this->getEditor();
+ $entities = $this->createEntityChainBelongingToUser($creator, $updater);
+ app()->make(UserRepo::class)->destroy($updater);
+ app()->make(PageRepo::class)->update($entities['page'], ['html' => '<p>Hello there!</p>']);
+
+ $this->checkEntitiesViewable($entities);
+ }
+
+ /**
+ * @param array<string, Entity> $entities
+ */
+ private function checkEntitiesViewable(array $entities)
+ {
+ // Check pages and books are visible.
+ $this->asAdmin();
+ foreach ($entities as $entity) {
+ $this->get($entity->getUrl())
+ ->assertStatus(200)
+ ->assertSee($entity->name);
+ }
+
+ // Check revision listing shows no errors.
+ $this->get($entities['page']->getUrl('/revisions'))->assertStatus(200);
+ }
+}
+++ /dev/null
-<?php
-
-namespace Tests\Entity;
-
-use BookStack\Auth\UserRepo;
-use BookStack\Entities\Models\Book;
-use BookStack\Entities\Models\Bookshelf;
-use BookStack\Entities\Models\Chapter;
-use BookStack\Entities\Models\Page;
-use BookStack\Entities\Repos\PageRepo;
-use Carbon\Carbon;
-use Tests\BrowserKitTest;
-
-class EntityTest extends BrowserKitTest
-{
- public function test_entity_creation()
- {
- // Test Creation
- $book = $this->bookCreation();
- $chapter = $this->chapterCreation($book);
- $this->pageCreation($chapter);
-
- // Test Updating
- $this->bookUpdate($book);
- }
-
- public function bookUpdate(Book $book)
- {
- $newName = $book->name . ' Updated';
- $this->asAdmin()
- // Go to edit screen
- ->visit($book->getUrl() . '/edit')
- ->see($book->name)
- // Submit new name
- ->type($newName, '#name')
- ->press('Save Book')
- // Check page url and text
- ->seePageIs($book->getUrl() . '-updated')
- ->see($newName);
-
- return Book::find($book->id);
- }
-
- public function test_book_sort_page_shows()
- {
- $books = Book::all();
- $bookToSort = $books[0];
- $this->asAdmin()
- ->visit($bookToSort->getUrl())
- ->click('Sort')
- ->seePageIs($bookToSort->getUrl() . '/sort')
- ->seeStatusCode(200)
- ->see($bookToSort->name);
- }
-
- public function test_book_sort_item_returns_book_content()
- {
- $books = Book::all();
- $bookToSort = $books[0];
- $firstPage = $bookToSort->pages[0];
- $firstChapter = $bookToSort->chapters[0];
- $this->asAdmin()
- ->visit($bookToSort->getUrl() . '/sort-item')
- // Ensure book details are returned
- ->see($bookToSort->name)
- ->see($firstPage->name)
- ->see($firstChapter->name);
- }
-
- public function test_toggle_book_view()
- {
- $editor = $this->getEditor();
- setting()->putUser($editor, 'books_view_type', 'grid');
-
- $this->actingAs($editor)
- ->visit('/books')
- ->pageHasElement('.featured-image-container')
- ->submitForm('List View')
- // Check redirection.
- ->seePageIs('/books')
- ->pageNotHasElement('.featured-image-container');
-
- $this->actingAs($editor)
- ->visit('/books')
- ->submitForm('Grid View')
- ->seePageIs('/books')
- ->pageHasElement('.featured-image-container');
- }
-
- public function pageCreation($chapter)
- {
- $page = factory(Page::class)->make([
- 'name' => 'My First Page',
- ]);
-
- $this->asAdmin()
- // Navigate to page create form
- ->visit($chapter->getUrl())
- ->click('New Page');
-
- $draftPage = Page::where('draft', '=', true)->orderBy('created_at', 'desc')->first();
-
- $this->seePageIs($draftPage->getUrl())
- // Fill out form
- ->type($page->name, '#name')
- ->type($page->html, '#html')
- ->press('Save Page')
- // Check redirect and page
- ->seePageIs($chapter->book->getUrl() . '/page/my-first-page')
- ->see($page->name);
-
- $page = Page::where('slug', '=', 'my-first-page')->where('chapter_id', '=', $chapter->id)->first();
-
- return $page;
- }
-
- public function chapterCreation(Book $book)
- {
- $chapter = factory(Chapter::class)->make([
- 'name' => 'My First Chapter',
- ]);
-
- $this->asAdmin()
- // Navigate to chapter create page
- ->visit($book->getUrl())
- ->click('New Chapter')
- ->seePageIs($book->getUrl() . '/create-chapter')
- // Fill out form
- ->type($chapter->name, '#name')
- ->type($chapter->description, '#description')
- ->press('Save Chapter')
- // Check redirect and landing page
- ->seePageIs($book->getUrl() . '/chapter/my-first-chapter')
- ->see($chapter->name)->see($chapter->description);
-
- $chapter = Chapter::where('slug', '=', 'my-first-chapter')->where('book_id', '=', $book->id)->first();
-
- return $chapter;
- }
-
- public function bookCreation()
- {
- $book = factory(Book::class)->make([
- 'name' => 'My First Book',
- ]);
- $this->asAdmin()
- ->visit('/books')
- // Choose to create a book
- ->click('Create New Book')
- ->seePageIs('/create-book')
- // Fill out form & save
- ->type($book->name, '#name')
- ->type($book->description, '#description')
- ->press('Save Book')
- // Check it redirects correctly
- ->seePageIs('/books/my-first-book')
- ->see($book->name)->see($book->description);
-
- // Ensure duplicate names are given different slugs
- $this->asAdmin()
- ->visit('/create-book')
- ->type($book->name, '#name')
- ->type($book->description, '#description')
- ->press('Save Book');
-
- $expectedPattern = '/\/books\/my-first-book-[0-9a-zA-Z]{3}/';
- $this->assertMatchesRegularExpression($expectedPattern, $this->currentUri, "Did not land on expected page [$expectedPattern].\n");
-
- $book = Book::where('slug', '=', 'my-first-book')->first();
-
- return $book;
- }
-
- public function test_entities_viewable_after_creator_deletion()
- {
- // Create required assets and revisions
- $creator = $this->getEditor();
- $updater = $this->getEditor();
- $entities = $this->createEntityChainBelongingToUser($creator, $updater);
- $this->actingAs($creator);
- app(UserRepo::class)->destroy($creator);
- app(PageRepo::class)->update($entities['page'], ['html' => '<p>hello!</p>>']);
-
- $this->checkEntitiesViewable($entities);
- }
-
- public function test_entities_viewable_after_updater_deletion()
- {
- // Create required assets and revisions
- $creator = $this->getEditor();
- $updater = $this->getEditor();
- $entities = $this->createEntityChainBelongingToUser($creator, $updater);
- $this->actingAs($updater);
- app(UserRepo::class)->destroy($updater);
- app(PageRepo::class)->update($entities['page'], ['html' => '<p>Hello there!</p>']);
-
- $this->checkEntitiesViewable($entities);
- }
-
- private function checkEntitiesViewable($entities)
- {
- // Check pages and books are visible.
- $this->asAdmin();
- $this->visit($entities['book']->getUrl())->seeStatusCode(200)
- ->visit($entities['chapter']->getUrl())->seeStatusCode(200)
- ->visit($entities['page']->getUrl())->seeStatusCode(200);
- // Check revision listing shows no errors.
- $this->visit($entities['page']->getUrl())
- ->click('Revisions')->seeStatusCode(200);
- }
-
- public function test_recently_updated_pages_view()
- {
- $user = $this->getEditor();
- $content = $this->createEntityChainBelongingToUser($user);
-
- $this->asAdmin()->visit('/pages/recently-updated')
- ->seeInNthElement('.entity-list .page', 0, $content['page']->name);
- }
-
- public function test_old_page_slugs_redirect_to_new_pages()
- {
- $page = Page::first();
- $pageUrl = $page->getUrl();
- $newPageUrl = '/books/' . $page->book->slug . '/page/super-test-page';
- // Need to save twice since revisions are not generated in seeder.
- $this->asAdmin()->visit($pageUrl)
- ->clickInElement('#content', 'Edit')
- ->type('super test', '#name')
- ->press('Save Page');
-
- $page = Page::first();
- $pageUrl = $page->getUrl();
-
- // Second Save
- $this->visit($pageUrl)
- ->clickInElement('#content', 'Edit')
- ->type('super test page', '#name')
- ->press('Save Page')
- // Check redirect
- ->seePageIs($newPageUrl);
-
- $this->visit($pageUrl)
- ->seePageIs($newPageUrl);
- }
-
- public function test_recently_updated_pages_on_home()
- {
- $page = Page::orderBy('updated_at', 'asc')->first();
- Page::where('id', '!=', $page->id)->update([
- 'updated_at' => Carbon::now()->subSecond(1),
- ]);
- $this->asAdmin()->visit('/')
- ->dontSeeInElement('#recently-updated-pages', $page->name);
- $this->visit($page->getUrl() . '/edit')
- ->press('Save Page')
- ->visit('/')
- ->seeInElement('#recently-updated-pages', $page->name);
- }
-
- public function test_slug_multi_byte_url_safe()
- {
- $book = $this->newBook([
- 'name' => 'информация',
- ]);
-
- $this->assertEquals('informatsiya', $book->slug);
-
- $book = $this->newBook([
- 'name' => '¿Qué?',
- ]);
-
- $this->assertEquals('que', $book->slug);
- }
-
- public function test_slug_format()
- {
- $book = $this->newBook([
- 'name' => 'PartA / PartB / PartC',
- ]);
-
- $this->assertEquals('parta-partb-partc', $book->slug);
- }
-
- public function test_shelf_cancel_creation_returns_to_correct_place()
- {
- $shelf = Bookshelf::first();
-
- // Cancel button from shelf goes back to shelf
- $this->asEditor()->visit($shelf->getUrl('/create-book'))
- ->see('Cancel')
- ->click('Cancel')
- ->seePageIs($shelf->getUrl());
-
- // Cancel button from books goes back to books
- $this->asEditor()->visit('/create-book')
- ->see('Cancel')
- ->click('Cancel')
- ->seePageIs('/books');
-
- // Cancel button from book edit goes back to book
- $book = Book::first();
-
- $this->asEditor()->visit($book->getUrl('/edit'))
- ->see('Cancel')
- ->click('Cancel')
- ->seePageIs($book->getUrl());
- }
-
- public function test_page_within_chapter_deletion_returns_to_chapter()
- {
- $chapter = Chapter::query()->first();
- $page = $chapter->pages()->first();
-
- $this->asEditor()->visit($page->getUrl('/delete'))
- ->submitForm('Confirm')
- ->seePageIs($chapter->getUrl());
- }
-}
namespace Tests\Entity;
+use BookStack\Auth\Role;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Page;
$resp->assertSee('# ' . $chapter->name);
$resp->assertSee('# ' . $page->name);
}
+
+ public function test_export_option_only_visible_and_accessible_with_permission()
+ {
+ $book = Book::query()->whereHas('pages')->whereHas('chapters')->first();
+ $chapter = $book->chapters()->first();
+ $page = $chapter->pages()->first();
+ $entities = [$book, $chapter, $page];
+ $user = $this->getViewer();
+ $this->actingAs($user);
+
+ foreach ($entities as $entity) {
+ $resp = $this->get($entity->getUrl());
+ $resp->assertSee('/export/pdf');
+ }
+
+ /** @var Role $role */
+ $this->removePermissionFromUser($user, 'content-export');
+
+ foreach ($entities as $entity) {
+ $resp = $this->get($entity->getUrl());
+ $resp->assertDontSee('/export/pdf');
+ $resp = $this->get($entity->getUrl('/export/pdf'));
+ $this->assertPermissionError($resp);
+ }
+ }
+
+ public function test_wkhtmltopdf_only_used_when_allow_untrusted_is_true()
+ {
+ /** @var Page $page */
+ $page = Page::query()->first();
+
+ config()->set('snappy.pdf.binary', '/abc123');
+ config()->set('app.allow_untrusted_server_fetching', false);
+
+ $resp = $this->asEditor()->get($page->getUrl('/export/pdf'));
+ $resp->assertStatus(200); // Sucessful response with invalid snappy binary indicates dompdf usage.
+
+ config()->set('app.allow_untrusted_server_fetching', true);
+ $resp = $this->get($page->getUrl('/export/pdf'));
+ $resp->assertStatus(500); // Bad response indicates wkhtml usage
+ }
}
+++ /dev/null
-<?php
-
-namespace Tests\Entity;
-
-use Tests\BrowserKitTest;
-
-class MarkdownTest extends BrowserKitTest
-{
- protected $page;
-
- public function setUp(): void
- {
- parent::setUp();
- $this->page = \BookStack\Entities\Models\Page::first();
- }
-
- protected function setMarkdownEditor()
- {
- $this->setSettings(['app-editor' => 'markdown']);
- }
-
- public function test_default_editor_is_wysiwyg()
- {
- $this->assertEquals(setting('app-editor'), 'wysiwyg');
- $this->asAdmin()->visit($this->page->getUrl() . '/edit')
- ->pageHasElement('#html-editor');
- }
-
- public function test_markdown_setting_shows_markdown_editor()
- {
- $this->setMarkdownEditor();
- $this->asAdmin()->visit($this->page->getUrl() . '/edit')
- ->pageNotHasElement('#html-editor')
- ->pageHasElement('#markdown-editor');
- }
-
- public function test_markdown_content_given_to_editor()
- {
- $this->setMarkdownEditor();
- $mdContent = '# hello. This is a test';
- $this->page->markdown = $mdContent;
- $this->page->save();
- $this->asAdmin()->visit($this->page->getUrl() . '/edit')
- ->seeInField('markdown', $mdContent);
- }
-
- public function test_html_content_given_to_editor_if_no_markdown()
- {
- $this->setMarkdownEditor();
- $this->asAdmin()->visit($this->page->getUrl() . '/edit')
- ->seeInField('markdown', $this->page->html);
- }
-}
}
}
- public function test_iframe_js_and_base64_urls_are_removed()
+ public function test_js_and_base64_src_urls_are_removed()
{
$checks = [
'<iframe src="javascript:alert(document.cookie)"></iframe>',
+ '<iframe src="JavAScRipT:alert(document.cookie)"></iframe>',
+ '<iframe src="JavAScRipT:alert(document.cookie)"></iframe>',
'<iframe SRC=" javascript: alert(document.cookie)"></iframe>',
'<iframe src="data:text/html;base64,PHNjcmlwdD5hbGVydCgnaGVsbG8nKTwvc2NyaXB0Pg==" frameborder="0"></iframe>',
+ '<iframe src="DaTa:text/html;base64,PHNjcmlwdD5hbGVydCgnaGVsbG8nKTwvc2NyaXB0Pg==" frameborder="0"></iframe>',
'<iframe src=" data:text/html;base64,PHNjcmlwdD5hbGVydCgnaGVsbG8nKTwvc2NyaXB0Pg==" frameborder="0"></iframe>',
+ '<img src="javascript:alert(document.cookie)"/>',
+ '<img src="JavAScRipT:alert(document.cookie)"/>',
+ '<img src="JavAScRipT:alert(document.cookie)"/>',
+ '<img SRC=" javascript: alert(document.cookie)"/>',
+ '<img src="data:text/html;base64,PHNjcmlwdD5hbGVydCgnaGVsbG8nKTwvc2NyaXB0Pg=="/>',
+ '<img src="DaTa:text/html;base64,PHNjcmlwdD5hbGVydCgnaGVsbG8nKTwvc2NyaXB0Pg=="/>',
+ '<img src=" data:text/html;base64,PHNjcmlwdD5hbGVydCgnaGVsbG8nKTwvc2NyaXB0Pg=="/>',
'<iframe srcdoc="<script>window.alert(document.cookie)</script>"></iframe>',
+ '<iframe SRCdoc="<script>window.alert(document.cookie)</script>"></iframe>',
+ '<IMG SRC=`javascript:alert("RSnake says, \'XSS\'")`>',
];
$this->asEditor();
$pageView = $this->get($page->getUrl());
$pageView->assertStatus(200);
$pageView->assertElementNotContains('.page-content', '<iframe>');
+ $pageView->assertElementNotContains('.page-content', '<img');
$pageView->assertElementNotContains('.page-content', '</iframe>');
$pageView->assertElementNotContains('.page-content', 'src=');
$pageView->assertElementNotContains('.page-content', 'javascript:');
$checks = [
'<a id="xss" href="javascript:alert(document.cookie)>Click me</a>',
'<a id="xss" href="javascript: alert(document.cookie)>Click me</a>',
+ '<a id="xss" href="JaVaScRiPt: alert(document.cookie)>Click me</a>',
+ '<a id="xss" href=" JaVaScRiPt: alert(document.cookie)>Click me</a>',
];
$this->asEditor();
$pageView = $this->get($page->getUrl());
$pageView->assertStatus(200);
- $pageView->assertElementNotContains('.page-content', '<a id="xss">');
+ $pageView->assertElementNotContains('.page-content', '<a id="xss"');
$pageView->assertElementNotContains('.page-content', 'href=javascript:');
}
}
{
$checks = [
'<form><input id="xss" type=submit formaction=javascript:alert(document.domain) value=Submit><input></form>',
+ '<form ><button id="xss" formaction="JaVaScRiPt:alert(document.domain)">Click me</button></form>',
'<form ><button id="xss" formaction=javascript:alert(document.domain)>Click me</button></form>',
'<form id="xss" action=javascript:alert(document.domain)><input type=submit value=Submit></form>',
+ '<form id="xss" action="JaVaScRiPt:alert(document.domain)"><input type=submit value=Submit></form>',
];
$this->asEditor();
{
$checks = [
'<meta http-equiv="refresh" content="0; url=//external_url">',
+ '<meta http-equiv="refresh" ConTeNt="0; url=//external_url">',
+ '<meta http-equiv="refresh" content="0; UrL=//external_url">',
];
$this->asEditor();
{
$checks = [
'<p onclick="console.log(\'test\')">Hello</p>',
+ '<p OnCliCk="console.log(\'test\')">Hello</p>',
'<div>Lorem ipsum dolor sit amet.</div><p onclick="console.log(\'test\')">Hello</p>',
'<div>Lorem ipsum dolor sit amet.<p onclick="console.log(\'test\')">Hello</p></div>',
'<div><div><div><div>Lorem ipsum dolor sit amet.<p onclick="console.log(\'test\')">Hello</p></div></div></div></div>',
'<div onclick="console.log(\'test\')">Lorem ipsum dolor sit amet.</div><p onclick="console.log(\'test\')">Hello</p><div></div>',
'<a a="<img src=1 onerror=\'alert(1)\'> ',
+ '\<a onclick="alert(document.cookie)"\>xss link\</a\>',
];
$this->asEditor();
$pageView->assertDontSee('abc123abc123');
}
+ public function test_svg_xlink_hrefs_are_removed()
+ {
+ $checks = [
+ '<svg id="test" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100" height="100"><a xlink:href="javascript:alert(document.domain)"><rect x="0" y="0" width="100" height="100" /></a></svg>',
+ '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><use xlink:href="data:application/xml;base64 ,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj4KPGRlZnM+CjxjaXJjbGUgaWQ9InRlc3QiIHI9IjAiIGN4PSIwIiBjeT0iMCIgc3R5bGU9ImZpbGw6ICNGMDAiPgo8c2V0IGF0dHJpYnV0ZU5hbWU9ImZpbGwiIGF0dHJpYnV0ZVR5cGU9IkNTUyIgb25iZWdpbj0nYWxlcnQoZG9jdW1lbnQuZG9tYWluKScKb25lbmQ9J2FsZXJ0KCJvbmVuZCIpJyB0bz0iIzAwRiIgYmVnaW49IjBzIiBkdXI9Ijk5OXMiIC8+CjwvY2lyY2xlPgo8L2RlZnM+Cjx1c2UgeGxpbms6aHJlZj0iI3Rlc3QiLz4KPC9zdmc+#test"/></svg>',
+ ];
+
+ $this->asEditor();
+ $page = Page::query()->first();
+
+ foreach ($checks as $check) {
+ $page->html = $check;
+ $page->save();
+
+ $pageView = $this->get($page->getUrl());
+ $pageView->assertStatus(200);
+ $pageView->assertElementNotContains('.page-content', 'alert');
+ $pageView->assertElementNotContains('.page-content', 'xlink:href');
+ $pageView->assertElementNotContains('.page-content', 'application/xml');
+ }
+ }
+
public function test_page_inline_on_attributes_show_if_configured()
{
$this->asEditor();
$this->assertStringContainsString('type="checkbox"', $page->html);
$pageView = $this->get($page->getUrl());
- $pageView->assertElementExists('.page-content input[type=checkbox]');
+ $pageView->assertElementExists('.page-content li.task-list-item input[type=checkbox]');
+ $pageView->assertElementExists('.page-content li.task-list-item input[type=checkbox][checked=checked]');
}
public function test_page_markdown_strikethrough_rendering()
namespace Tests\Entity;
+use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Page;
use BookStack\Entities\Repos\PageRepo;
-use Tests\BrowserKitTest;
+use Tests\TestCase;
-class PageDraftTest extends BrowserKitTest
+class PageDraftTest extends TestCase
{
+ /**
+ * @var Page
+ */
protected $page;
/**
public function setUp(): void
{
parent::setUp();
- $this->page = \BookStack\Entities\Models\Page::first();
- $this->pageRepo = app(PageRepo::class);
+ $this->page = Page::query()->first();
+ $this->pageRepo = app()->make(PageRepo::class);
}
public function test_draft_content_shows_if_available()
{
$addedContent = '<p>test message content</p>';
- $this->asAdmin()->visit($this->page->getUrl('/edit'))
- ->dontSeeInField('html', $addedContent);
+
+ $this->asAdmin()->get($this->page->getUrl('/edit'))
+ ->assertElementNotContains('[name="html"]', $addedContent);
$newContent = $this->page->html . $addedContent;
$this->pageRepo->updatePageDraft($this->page, ['html' => $newContent]);
- $this->asAdmin()->visit($this->page->getUrl('/edit'))
- ->seeInField('html', $newContent);
+ $this->asAdmin()->get($this->page->getUrl('/edit'))
+ ->assertElementContains('[name="html"]', $newContent);
}
public function test_draft_not_visible_by_others()
{
$addedContent = '<p>test message content</p>';
- $this->asAdmin()->visit($this->page->getUrl('/edit'))
- ->dontSeeInField('html', $addedContent);
+ $this->asAdmin()->get($this->page->getUrl('/edit'))
+ ->assertElementNotContains('[name="html"]', $addedContent);
$newContent = $this->page->html . $addedContent;
$newUser = $this->getEditor();
$this->pageRepo->updatePageDraft($this->page, ['html' => $newContent]);
- $this->actingAs($newUser)->visit($this->page->getUrl('/edit'))
- ->dontSeeInField('html', $newContent);
+ $this->actingAs($newUser)->get($this->page->getUrl('/edit'))
+ ->assertElementNotContains('[name="html"]', $newContent);
}
public function test_alert_message_shows_if_editing_draft()
{
$this->asAdmin();
$this->pageRepo->updatePageDraft($this->page, ['html' => 'test content']);
- $this->asAdmin()->visit($this->page->getUrl('/edit'))
- ->see('You are currently editing a draft');
+ $this->asAdmin()->get($this->page->getUrl('/edit'))
+ ->assertSee('You are currently editing a draft');
}
public function test_alert_message_shows_if_someone_else_editing()
{
- $nonEditedPage = \BookStack\Entities\Models\Page::take(10)->get()->last();
+ $nonEditedPage = Page::query()->take(10)->get()->last();
$addedContent = '<p>test message content</p>';
- $this->asAdmin()->visit($this->page->getUrl('/edit'))
- ->dontSeeInField('html', $addedContent);
+ $this->asAdmin()->get($this->page->getUrl('/edit'))
+ ->assertElementNotContains('[name="html"]', $addedContent);
$newContent = $this->page->html . $addedContent;
$newUser = $this->getEditor();
$this->pageRepo->updatePageDraft($this->page, ['html' => $newContent]);
$this->actingAs($newUser)
- ->visit($this->page->getUrl('/edit'))
- ->see('Admin has started editing this page');
+ ->get($this->page->getUrl('/edit'))
+ ->assertSee('Admin has started editing this page');
$this->flushSession();
- $this->visit($nonEditedPage->getUrl() . '/edit')
- ->dontSeeInElement('.notification', 'Admin has started editing this page');
+ $this->get($nonEditedPage->getUrl() . '/edit')
+ ->assertElementNotContains('.notification', 'Admin has started editing this page');
}
public function test_draft_pages_show_on_homepage()
{
- $book = \BookStack\Entities\Models\Book::first();
- $this->asAdmin()->visit('/')
- ->dontSeeInElement('#recent-drafts', 'New Page')
- ->visit($book->getUrl() . '/create-page')
- ->visit('/')
- ->seeInElement('#recent-drafts', 'New Page');
+ /** @var Book $book */
+ $book = Book::query()->first();
+ $this->asAdmin()->get('/')
+ ->assertElementNotContains('#recent-drafts', 'New Page');
+
+ $this->get($book->getUrl() . '/create-page');
+
+ $this->get('/')->assertElementContains('#recent-drafts', 'New Page');
}
public function test_draft_pages_not_visible_by_others()
{
- $book = \BookStack\Entities\Models\Book::first();
+ /** @var Book $book */
+ $book = Book::query()->first();
$chapter = $book->chapters->first();
$newUser = $this->getEditor();
- $this->actingAs($newUser)->visit('/')
- ->visit($book->getUrl('/create-page'))
- ->visit($chapter->getUrl('/create-page'))
- ->visit($book->getUrl())
- ->seeInElement('.book-contents', 'New Page');
-
- $this->asAdmin()
- ->visit($book->getUrl())
- ->dontSeeInElement('.book-contents', 'New Page')
- ->visit($chapter->getUrl())
- ->dontSeeInElement('.book-contents', 'New Page');
+ $this->actingAs($newUser)->get($book->getUrl('/create-page'));
+ $this->get($chapter->getUrl('/create-page'));
+ $this->get($book->getUrl())
+ ->assertElementContains('.book-contents', 'New Page');
+
+ $this->asAdmin()->get($book->getUrl())
+ ->assertElementNotContains('.book-contents', 'New Page');
+ $this->get($chapter->getUrl())
+ ->assertElementNotContains('.book-contents', 'New Page');
}
public function test_page_html_in_ajax_fetch_response()
{
$this->asAdmin();
+ /** @var Page $page */
$page = Page::query()->first();
- $this->getJson('/ajax/page/' . $page->id);
- $this->seeJson([
+ $this->getJson('/ajax/page/' . $page->id)->assertJson([
'html' => $page->html,
]);
}
--- /dev/null
+<?php
+
+namespace Tests\Entity;
+
+use BookStack\Entities\Models\Book;
+use BookStack\Entities\Models\Page;
+use Tests\TestCase;
+
+class PageEditorTest extends TestCase
+{
+ /** @var Page */
+ protected $page;
+
+ public function setUp(): void
+ {
+ parent::setUp();
+ $this->page = Page::query()->first();
+ }
+
+ public function test_default_editor_is_wysiwyg()
+ {
+ $this->assertEquals('wysiwyg', setting('app-editor'));
+ $this->asAdmin()->get($this->page->getUrl() . '/edit')
+ ->assertElementExists('#html-editor');
+ }
+
+ public function test_markdown_setting_shows_markdown_editor()
+ {
+ $this->setSettings(['app-editor' => 'markdown']);
+ $this->asAdmin()->get($this->page->getUrl() . '/edit')
+ ->assertElementNotExists('#html-editor')
+ ->assertElementExists('#markdown-editor');
+ }
+
+ public function test_markdown_content_given_to_editor()
+ {
+ $this->setSettings(['app-editor' => 'markdown']);
+
+ $mdContent = '# hello. This is a test';
+ $this->page->markdown = $mdContent;
+ $this->page->save();
+
+ $this->asAdmin()->get($this->page->getUrl() . '/edit')
+ ->assertElementContains('[name="markdown"]', $mdContent);
+ }
+
+ public function test_html_content_given_to_editor_if_no_markdown()
+ {
+ $this->setSettings(['app-editor' => 'markdown']);
+ $this->asAdmin()->get($this->page->getUrl() . '/edit')
+ ->assertElementContains('[name="markdown"]', $this->page->html);
+ }
+
+ public function test_empty_markdown_still_saves_without_error()
+ {
+ $this->setSettings(['app-editor' => 'markdown']);
+ /** @var Book $book */
+ $book = Book::query()->first();
+
+ $this->asEditor()->get($book->getUrl('/create-page'));
+ $draft = Page::query()->where('book_id', '=', $book->id)
+ ->where('draft', '=', true)->first();
+
+ $details = [
+ 'name' => 'my page',
+ 'markdown' => '',
+ ];
+ $resp = $this->post($book->getUrl("/draft/{$draft->id}"), $details);
+ $resp->assertRedirect();
+
+ $this->assertDatabaseHas('pages', [
+ 'markdown' => $details['markdown'],
+ 'id' => $draft->id,
+ 'draft' => false,
+ ]);
+ }
+}
namespace Tests\Entity;
use BookStack\Entities\Models\Book;
+use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Page;
+use Carbon\Carbon;
use Tests\TestCase;
class PageTest extends TestCase
{
+ public function test_create()
+ {
+ /** @var Chapter $chapter */
+ $chapter = Chapter::query()->first();
+ $page = factory(Page::class)->make([
+ 'name' => 'My First Page',
+ ]);
+
+ $resp = $this->asEditor()->get($chapter->getUrl());
+ $resp->assertElementContains('a[href="' . $chapter->getUrl('/create-page') . '"]', 'New Page');
+
+ $resp = $this->get($chapter->getUrl('/create-page'));
+ /** @var Page $draftPage */
+ $draftPage = Page::query()
+ ->where('draft', '=', true)
+ ->orderBy('created_at', 'desc')
+ ->first();
+ $resp->assertRedirect($draftPage->getUrl());
+
+ $resp = $this->get($draftPage->getUrl());
+ $resp->assertElementContains('form[action="' . $draftPage->getUrl() . '"][method="POST"]', 'Save Page');
+
+ $resp = $this->post($draftPage->getUrl(), $draftPage->only('name', 'html'));
+ $draftPage->refresh();
+ $resp->assertRedirect($draftPage->getUrl());
+ }
+
public function test_page_view_when_creator_is_deleted_but_owner_exists()
{
$page = Page::query()->first();
]);
}
- public function test_empty_markdown_still_saves_without_error()
+ public function test_old_page_slugs_redirect_to_new_pages()
{
- $this->setSettings(['app-editor' => 'markdown']);
- $book = Book::query()->first();
+ /** @var Page $page */
+ $page = Page::query()->first();
- $this->asEditor()->get($book->getUrl('/create-page'));
- $draft = Page::query()->where('book_id', '=', $book->id)
- ->where('draft', '=', true)->first();
+ // Need to save twice since revisions are not generated in seeder.
+ $this->asAdmin()->put($page->getUrl(), [
+ 'name' => 'super test',
+ 'html' => '<p></p>',
+ ]);
- $details = [
- 'name' => 'my page',
- 'markdown' => '',
- ];
- $resp = $this->post($book->getUrl("/draft/{$draft->id}"), $details);
- $resp->assertRedirect();
+ $page->refresh();
+ $pageUrl = $page->getUrl();
- $this->assertDatabaseHas('pages', [
- 'markdown' => $details['markdown'],
- 'id' => $draft->id,
- 'draft' => false,
+ $this->put($pageUrl, [
+ 'name' => 'super test page',
+ 'html' => '<p></p>',
+ ]);
+
+ $this->get($pageUrl)
+ ->assertRedirect("/books/{$page->book->slug}/page/super-test-page");
+ }
+
+ public function test_page_within_chapter_deletion_returns_to_chapter()
+ {
+ /** @var Chapter $chapter */
+ $chapter = Chapter::query()->first();
+ $page = $chapter->pages()->first();
+
+ $this->asEditor()->delete($page->getUrl())
+ ->assertRedirect($chapter->getUrl());
+ }
+
+ public function test_recently_updated_pages_view()
+ {
+ $user = $this->getEditor();
+ $content = $this->createEntityChainBelongingToUser($user);
+
+ $this->asAdmin()->get('/pages/recently-updated')
+ ->assertElementContains('.entity-list .page:nth-child(1)', $content['page']->name);
+ }
+
+ public function test_recently_updated_pages_on_home()
+ {
+ /** @var Page $page */
+ $page = Page::query()->orderBy('updated_at', 'asc')->first();
+ Page::query()->where('id', '!=', $page->id)->update([
+ 'updated_at' => Carbon::now()->subSecond(1),
]);
+
+ $this->asAdmin()->get('/')
+ ->assertElementNotContains('#recently-updated-pages', $page->name);
+
+ $this->put($page->getUrl(), [
+ 'name' => $page->name,
+ 'html' => $page->html,
+ ]);
+
+ $this->get('/')
+ ->assertElementContains('#recently-updated-pages', $page->name);
}
}
$this->assertEquals($newBook->id, $pageToCheck->book_id);
}
+ public function test_book_sort_page_shows()
+ {
+ /** @var Book $bookToSort */
+ $bookToSort = Book::query()->first();
+
+ $resp = $this->asAdmin()->get($bookToSort->getUrl());
+ $resp->assertElementExists('a[href="' . $bookToSort->getUrl('/sort') . '"]');
+
+ $resp = $this->get($bookToSort->getUrl('/sort'));
+ $resp->assertStatus(200);
+ $resp->assertSee($bookToSort->name);
+ }
+
public function test_book_sort()
{
$oldBook = Book::query()->first();
$checkResp = $this->get(Page::find($checkPage->id)->getUrl());
$checkResp->assertSee($newBook->name);
}
+
+ public function test_book_sort_item_returns_book_content()
+ {
+ $books = Book::all();
+ $bookToSort = $books[0];
+ $firstPage = $bookToSort->pages[0];
+ $firstChapter = $bookToSort->chapters[0];
+
+ $resp = $this->asAdmin()->get($bookToSort->getUrl() . '/sort-item');
+
+ // Ensure book details are returned
+ $resp->assertSee($bookToSort->name);
+ $resp->assertSee($firstPage->name);
+ $resp->assertSee($firstChapter->name);
+ }
+
+ public function test_pages_in_book_show_sorted_by_priority()
+ {
+ /** @var Book $book */
+ $book = Book::query()->whereHas('pages')->first();
+ $book->chapters()->forceDelete();
+ /** @var Page[] $pages */
+ $pages = $book->pages()->where('chapter_id', '=', 0)->take(2)->get();
+ $book->pages()->whereNotIn('id', $pages->pluck('id'))->delete();
+
+ $resp = $this->asEditor()->get($book->getUrl());
+ $resp->assertElementContains('.content-wrap a.page:nth-child(1)', $pages[0]->name);
+ $resp->assertElementContains('.content-wrap a.page:nth-child(2)', $pages[1]->name);
+
+ $pages[0]->forceFill(['priority' => 10])->save();
+ $pages[1]->forceFill(['priority' => 5])->save();
+
+ $resp = $this->asEditor()->get($book->getUrl());
+ $resp->assertElementContains('.content-wrap a.page:nth-child(1)', $pages[1]->name);
+ $resp->assertElementContains('.content-wrap a.page:nth-child(2)', $pages[0]->name);
+ }
}
use BookStack\Auth\Role;
use BookStack\Auth\User;
use BookStack\Entities\Models\Bookshelf;
+use BookStack\Entities\Models\Page;
class HomepageTest extends TestCase
{
$pageDeleteReq->assertSessionMissing('error');
}
+ public function test_custom_homepage_renders_includes()
+ {
+ $this->asEditor();
+ /** @var Page $included */
+ $included = Page::query()->first();
+ $content = str_repeat('This is the body content of my custom homepage.', 20);
+ $included->html = $content;
+ $included->save();
+
+ $name = 'My custom homepage';
+ $customPage = $this->newPage(['name' => $name, 'html' => '{{@' . $included->id . '}}']);
+ $this->setSettings(['app-homepage' => $customPage->id]);
+ $this->setSettings(['app-homepage-type' => 'page']);
+
+ $homeVisit = $this->get('/');
+ $homeVisit->assertSee($name);
+ $homeVisit->assertSee($content);
+ }
+
public function test_set_book_homepage()
{
$editor = $this->getEditor();
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\Page;
use Illuminate\Support\Str;
-use Tests\BrowserKitTest;
+use Tests\TestCase;
-class EntityPermissionsTest extends BrowserKitTest
+class EntityPermissionsTest extends TestCase
{
/**
* @var User
public function test_bookshelf_view_restriction()
{
- $shelf = Bookshelf::first();
+ /** @var Bookshelf $shelf */
+ $shelf = Bookshelf::query()->first();
$this->actingAs($this->user)
- ->visit($shelf->getUrl())
- ->seePageIs($shelf->getUrl());
+ ->get($shelf->getUrl())
+ ->assertStatus(200);
$this->setRestrictionsForTestRoles($shelf, []);
- $this->forceVisit($shelf->getUrl())
- ->see('Bookshelf not found');
+ $this->followingRedirects()->get($shelf->getUrl())
+ ->assertSee('Bookshelf not found');
$this->setRestrictionsForTestRoles($shelf, ['view']);
- $this->visit($shelf->getUrl())
- ->see($shelf->name);
+ $this->get($shelf->getUrl())
+ ->assertSee($shelf->name);
}
public function test_bookshelf_update_restriction()
{
- $shelf = Bookshelf::first();
+ /** @var Bookshelf $shelf */
+ $shelf = Bookshelf::query()->first();
$this->actingAs($this->user)
- ->visit($shelf->getUrl('/edit'))
- ->see('Edit Book');
+ ->get($shelf->getUrl('/edit'))
+ ->assertSee('Edit Book');
$this->setRestrictionsForTestRoles($shelf, ['view', 'delete']);
- $this->forceVisit($shelf->getUrl('/edit'))
- ->see('You do not have permission')->seePageIs('/');
+ $resp = $this->get($shelf->getUrl('/edit'))
+ ->assertRedirect('/');
+ $this->followRedirects($resp)->assertSee('You do not have permission');
$this->setRestrictionsForTestRoles($shelf, ['view', 'update']);
- $this->visit($shelf->getUrl('/edit'))
- ->seePageIs($shelf->getUrl('/edit'));
+ $this->get($shelf->getUrl('/edit'))
+ ->assertOk();
}
public function test_bookshelf_delete_restriction()
{
- $shelf = Book::first();
+ /** @var Bookshelf $shelf */
+ $shelf = Bookshelf::query()->first();
$this->actingAs($this->user)
- ->visit($shelf->getUrl('/delete'))
- ->see('Delete Book');
+ ->get($shelf->getUrl('/delete'))
+ ->assertSee('Delete Book');
$this->setRestrictionsForTestRoles($shelf, ['view', 'update']);
- $this->forceVisit($shelf->getUrl('/delete'))
- ->see('You do not have permission')->seePageIs('/');
+ $this->get($shelf->getUrl('/delete'))->assertRedirect('/');
+ $this->get('/')->assertSee('You do not have permission');
$this->setRestrictionsForTestRoles($shelf, ['view', 'delete']);
- $this->visit($shelf->getUrl('/delete'))
- ->seePageIs($shelf->getUrl('/delete'))->see('Delete Book');
+ $this->get($shelf->getUrl('/delete'))
+ ->assertOk()
+ ->assertSee('Delete Book');
}
public function test_book_view_restriction()
{
- $book = Book::first();
+ /** @var Book $book */
+ $book = Book::query()->first();
$bookPage = $book->pages->first();
$bookChapter = $book->chapters->first();
$bookUrl = $book->getUrl();
$this->actingAs($this->user)
- ->visit($bookUrl)
- ->seePageIs($bookUrl);
+ ->get($bookUrl)
+ ->assertOk();
$this->setRestrictionsForTestRoles($book, []);
- $this->forceVisit($bookUrl)
- ->see('Book not found');
- $this->forceVisit($bookPage->getUrl())
- ->see('Page not found');
- $this->forceVisit($bookChapter->getUrl())
- ->see('Chapter not found');
+ $this->followingRedirects()->get($bookUrl)
+ ->assertSee('Book not found');
+ $this->followingRedirects()->get($bookPage->getUrl())
+ ->assertSee('Page not found');
+ $this->followingRedirects()->get($bookChapter->getUrl())
+ ->assertSee('Chapter not found');
$this->setRestrictionsForTestRoles($book, ['view']);
- $this->visit($bookUrl)
- ->see($book->name);
- $this->visit($bookPage->getUrl())
- ->see($bookPage->name);
- $this->visit($bookChapter->getUrl())
- ->see($bookChapter->name);
+ $this->get($bookUrl)
+ ->assertSee($book->name);
+ $this->get($bookPage->getUrl())
+ ->assertSee($bookPage->name);
+ $this->get($bookChapter->getUrl())
+ ->assertSee($bookChapter->name);
}
public function test_book_create_restriction()
{
- $book = Book::first();
+ /** @var Book $book */
+ $book = Book::query()->first();
$bookUrl = $book->getUrl();
$this->actingAs($this->viewer)
- ->visit($bookUrl)
- ->dontSeeInElement('.actions', 'New Page')
- ->dontSeeInElement('.actions', 'New Chapter');
+ ->get($bookUrl)
+ ->assertElementNotContains('.actions', 'New Page')
+ ->assertElementNotContains('.actions', 'New Chapter');
$this->actingAs($this->user)
- ->visit($bookUrl)
- ->seeInElement('.actions', 'New Page')
- ->seeInElement('.actions', 'New Chapter');
+ ->get($bookUrl)
+ ->assertElementContains('.actions', 'New Page')
+ ->assertElementContains('.actions', 'New Chapter');
$this->setRestrictionsForTestRoles($book, ['view', 'delete', 'update']);
- $this->forceVisit($bookUrl . '/create-chapter')
- ->see('You do not have permission')->seePageIs('/');
- $this->forceVisit($bookUrl . '/create-page')
- ->see('You do not have permission')->seePageIs('/');
- $this->visit($bookUrl)->dontSeeInElement('.actions', 'New Page')
- ->dontSeeInElement('.actions', 'New Chapter');
+ $this->get($bookUrl . '/create-chapter')->assertRedirect('/');
+ $this->get('/')->assertSee('You do not have permission');
+
+ $this->get($bookUrl . '/create-page')->assertRedirect('/');
+ $this->get('/')->assertSee('You do not have permission');
+
+ $this->get($bookUrl)
+ ->assertElementNotContains('.actions', 'New Page')
+ ->assertElementNotContains('.actions', 'New Chapter');
$this->setRestrictionsForTestRoles($book, ['view', 'create']);
- $this->visit($bookUrl . '/create-chapter')
- ->type('test chapter', 'name')
- ->type('test description for chapter', 'description')
- ->press('Save Chapter')
- ->seePageIs($bookUrl . '/chapter/test-chapter');
- $this->visit($bookUrl . '/create-page')
- ->type('test page', 'name')
- ->type('test content', 'html')
- ->press('Save Page')
- ->seePageIs($bookUrl . '/page/test-page');
- $this->visit($bookUrl)->seeInElement('.actions', 'New Page')
- ->seeInElement('.actions', 'New Chapter');
+ $resp = $this->post($book->getUrl('/create-chapter'), [
+ 'name' => 'test chapter',
+ 'description' => 'desc',
+ ]);
+ $resp->assertRedirect($book->getUrl('/chapter/test-chapter'));
+
+ $this->get($book->getUrl('/create-page'));
+ /** @var Page $page */
+ $page = Page::query()->where('draft', '=', true)->orderBy('id', 'desc')->first();
+ $resp = $this->post($page->getUrl(), [
+ 'name' => 'test page',
+ 'html' => 'test content',
+ ]);
+ $resp->assertRedirect($book->getUrl('/page/test-page'));
+
+ $this->get($bookUrl)
+ ->assertElementContains('.actions', 'New Page')
+ ->assertElementContains('.actions', 'New Chapter');
}
public function test_book_update_restriction()
{
- $book = Book::first();
+ /** @var Book $book */
+ $book = Book::query()->first();
$bookPage = $book->pages->first();
$bookChapter = $book->chapters->first();
$bookUrl = $book->getUrl();
$this->actingAs($this->user)
- ->visit($bookUrl . '/edit')
- ->see('Edit Book');
+ ->get($bookUrl . '/edit')
+ ->assertSee('Edit Book');
$this->setRestrictionsForTestRoles($book, ['view', 'delete']);
- $this->forceVisit($bookUrl . '/edit')
- ->see('You do not have permission')->seePageIs('/');
- $this->forceVisit($bookPage->getUrl() . '/edit')
- ->see('You do not have permission')->seePageIs('/');
- $this->forceVisit($bookChapter->getUrl() . '/edit')
- ->see('You do not have permission')->seePageIs('/');
+ $this->get($bookUrl . '/edit')->assertRedirect('/');
+ $this->get('/')->assertSee('You do not have permission');
+ $this->get($bookPage->getUrl() . '/edit')->assertRedirect('/');
+ $this->get('/')->assertSee('You do not have permission');
+ $this->get($bookChapter->getUrl() . '/edit')->assertRedirect('/');
+ $this->get('/')->assertSee('You do not have permission');
$this->setRestrictionsForTestRoles($book, ['view', 'update']);
- $this->visit($bookUrl . '/edit')
- ->seePageIs($bookUrl . '/edit');
- $this->visit($bookPage->getUrl() . '/edit')
- ->seePageIs($bookPage->getUrl() . '/edit');
- $this->visit($bookChapter->getUrl() . '/edit')
- ->see('Edit Chapter');
+ $this->get($bookUrl . '/edit')->assertOk();
+ $this->get($bookPage->getUrl() . '/edit')->assertOk();
+ $this->get($bookChapter->getUrl() . '/edit')->assertSee('Edit Chapter');
}
public function test_book_delete_restriction()
{
- $book = Book::first();
+ /** @var Book $book */
+ $book = Book::query()->first();
$bookPage = $book->pages->first();
$bookChapter = $book->chapters->first();
$bookUrl = $book->getUrl();
- $this->actingAs($this->user)
- ->visit($bookUrl . '/delete')
- ->see('Delete Book');
+ $this->actingAs($this->user)->get($bookUrl . '/delete')
+ ->assertSee('Delete Book');
$this->setRestrictionsForTestRoles($book, ['view', 'update']);
- $this->forceVisit($bookUrl . '/delete')
- ->see('You do not have permission')->seePageIs('/');
- $this->forceVisit($bookPage->getUrl() . '/delete')
- ->see('You do not have permission')->seePageIs('/');
- $this->forceVisit($bookChapter->getUrl() . '/delete')
- ->see('You do not have permission')->seePageIs('/');
+ $this->get($bookUrl . '/delete')->assertRedirect('/');
+ $this->get('/')->assertSee('You do not have permission');
+ $this->get($bookPage->getUrl() . '/delete')->assertRedirect('/');
+ $this->get('/')->assertSee('You do not have permission');
+ $this->get($bookChapter->getUrl() . '/delete')->assertRedirect('/');
+ $this->get('/')->assertSee('You do not have permission');
$this->setRestrictionsForTestRoles($book, ['view', 'delete']);
- $this->visit($bookUrl . '/delete')
- ->seePageIs($bookUrl . '/delete')->see('Delete Book');
- $this->visit($bookPage->getUrl() . '/delete')
- ->seePageIs($bookPage->getUrl() . '/delete')->see('Delete Page');
- $this->visit($bookChapter->getUrl() . '/delete')
- ->see('Delete Chapter');
+ $this->get($bookUrl . '/delete')->assertOk()->assertSee('Delete Book');
+ $this->get($bookPage->getUrl('/delete'))->assertOk()->assertSee('Delete Page');
+ $this->get($bookChapter->getUrl('/delete'))->assertSee('Delete Chapter');
}
public function test_chapter_view_restriction()
{
- $chapter = Chapter::first();
+ /** @var Chapter $chapter */
+ $chapter = Chapter::query()->first();
$chapterPage = $chapter->pages->first();
$chapterUrl = $chapter->getUrl();
- $this->actingAs($this->user)
- ->visit($chapterUrl)
- ->seePageIs($chapterUrl);
+ $this->actingAs($this->user)->get($chapterUrl)->assertOk();
$this->setRestrictionsForTestRoles($chapter, []);
- $this->forceVisit($chapterUrl)
- ->see('Chapter not found');
- $this->forceVisit($chapterPage->getUrl())
- ->see('Page not found');
+ $this->followingRedirects()->get($chapterUrl)->assertSee('Chapter not found');
+ $this->followingRedirects()->get($chapterPage->getUrl())->assertSee('Page not found');
$this->setRestrictionsForTestRoles($chapter, ['view']);
- $this->visit($chapterUrl)
- ->see($chapter->name);
- $this->visit($chapterPage->getUrl())
- ->see($chapterPage->name);
+ $this->get($chapterUrl)->assertSee($chapter->name);
+ $this->get($chapterPage->getUrl())->assertSee($chapterPage->name);
}
public function test_chapter_create_restriction()
{
- $chapter = Chapter::first();
+ /** @var Chapter $chapter */
+ $chapter = Chapter::query()->first();
$chapterUrl = $chapter->getUrl();
$this->actingAs($this->user)
- ->visit($chapterUrl)
- ->seeInElement('.actions', 'New Page');
+ ->get($chapterUrl)
+ ->assertElementContains('.actions', 'New Page');
$this->setRestrictionsForTestRoles($chapter, ['view', 'delete', 'update']);
- $this->forceVisit($chapterUrl . '/create-page')
- ->see('You do not have permission')->seePageIs('/');
- $this->visit($chapterUrl)->dontSeeInElement('.actions', 'New Page');
+ $this->get($chapterUrl . '/create-page')->assertRedirect('/');
+ $this->get('/')->assertSee('You do not have permission');
+ $this->get($chapterUrl)->assertElementNotContains('.actions', 'New Page');
$this->setRestrictionsForTestRoles($chapter, ['view', 'create']);
- $this->visit($chapterUrl . '/create-page')
- ->type('test page', 'name')
- ->type('test content', 'html')
- ->press('Save Page')
- ->seePageIs($chapter->book->getUrl() . '/page/test-page');
+ $this->get($chapter->getUrl('/create-page'));
+ /** @var Page $page */
+ $page = Page::query()->where('draft', '=', true)->orderBy('id', 'desc')->first();
+ $resp = $this->post($page->getUrl(), [
+ 'name' => 'test page',
+ 'html' => 'test content',
+ ]);
+ $resp->assertRedirect($chapter->book->getUrl('/page/test-page'));
- $this->visit($chapterUrl)->seeInElement('.actions', 'New Page');
+ $this->get($chapterUrl)->assertElementContains('.actions', 'New Page');
}
public function test_chapter_update_restriction()
{
- $chapter = Chapter::first();
+ /** @var Chapter $chapter */
+ $chapter = Chapter::query()->first();
$chapterPage = $chapter->pages->first();
$chapterUrl = $chapter->getUrl();
- $this->actingAs($this->user)
- ->visit($chapterUrl . '/edit')
- ->see('Edit Chapter');
+ $this->actingAs($this->user)->get($chapterUrl . '/edit')
+ ->assertSee('Edit Chapter');
$this->setRestrictionsForTestRoles($chapter, ['view', 'delete']);
- $this->forceVisit($chapterUrl . '/edit')
- ->see('You do not have permission')->seePageIs('/');
- $this->forceVisit($chapterPage->getUrl() . '/edit')
- ->see('You do not have permission')->seePageIs('/');
+ $this->get($chapterUrl . '/edit')->assertRedirect('/');
+ $this->get('/')->assertSee('You do not have permission');
+ $this->get($chapterPage->getUrl() . '/edit')->assertRedirect('/');
+ $this->get('/')->assertSee('You do not have permission');
$this->setRestrictionsForTestRoles($chapter, ['view', 'update']);
- $this->visit($chapterUrl . '/edit')
- ->seePageIs($chapterUrl . '/edit')->see('Edit Chapter');
- $this->visit($chapterPage->getUrl() . '/edit')
- ->seePageIs($chapterPage->getUrl() . '/edit');
+ $this->get($chapterUrl . '/edit')->assertOk()->assertSee('Edit Chapter');
+ $this->get($chapterPage->getUrl() . '/edit')->assertOk();
}
public function test_chapter_delete_restriction()
{
- $chapter = Chapter::first();
+ /** @var Chapter $chapter */
+ $chapter = Chapter::query()->first();
$chapterPage = $chapter->pages->first();
$chapterUrl = $chapter->getUrl();
$this->actingAs($this->user)
- ->visit($chapterUrl . '/delete')
- ->see('Delete Chapter');
+ ->get($chapterUrl . '/delete')
+ ->assertSee('Delete Chapter');
$this->setRestrictionsForTestRoles($chapter, ['view', 'update']);
- $this->forceVisit($chapterUrl . '/delete')
- ->see('You do not have permission')->seePageIs('/');
- $this->forceVisit($chapterPage->getUrl() . '/delete')
- ->see('You do not have permission')->seePageIs('/');
+ $this->get($chapterUrl . '/delete')->assertRedirect('/');
+ $this->get('/')->assertSee('You do not have permission');
+ $this->get($chapterPage->getUrl() . '/delete')->assertRedirect('/');
+ $this->get('/')->assertSee('You do not have permission');
$this->setRestrictionsForTestRoles($chapter, ['view', 'delete']);
- $this->visit($chapterUrl . '/delete')
- ->seePageIs($chapterUrl . '/delete')->see('Delete Chapter');
- $this->visit($chapterPage->getUrl() . '/delete')
- ->seePageIs($chapterPage->getUrl() . '/delete')->see('Delete Page');
+ $this->get($chapterUrl . '/delete')->assertOk()->assertSee('Delete Chapter');
+ $this->get($chapterPage->getUrl() . '/delete')->assertOk()->assertSee('Delete Page');
}
public function test_page_view_restriction()
{
- $page = Page::first();
+ /** @var Page $page */
+ $page = Page::query()->first();
$pageUrl = $page->getUrl();
- $this->actingAs($this->user)
- ->visit($pageUrl)
- ->seePageIs($pageUrl);
+ $this->actingAs($this->user)->get($pageUrl)->assertOk();
$this->setRestrictionsForTestRoles($page, ['update', 'delete']);
- $this->forceVisit($pageUrl)
- ->see('Page not found');
+ $this->get($pageUrl)->assertSee('Page not found');
$this->setRestrictionsForTestRoles($page, ['view']);
- $this->visit($pageUrl)
- ->see($page->name);
+ $this->get($pageUrl)->assertSee($page->name);
}
public function test_page_update_restriction()
{
- $page = Chapter::first();
+ /** @var Page $page */
+ $page = Page::query()->first();
$pageUrl = $page->getUrl();
$this->actingAs($this->user)
- ->visit($pageUrl . '/edit')
- ->seeInField('name', $page->name);
+ ->get($pageUrl . '/edit')
+ ->assertElementExists('input[name="name"][value="' . $page->name . '"]');
$this->setRestrictionsForTestRoles($page, ['view', 'delete']);
- $this->forceVisit($pageUrl . '/edit')
- ->see('You do not have permission')->seePageIs('/');
+ $this->get($pageUrl . '/edit')->assertRedirect('/');
+ $this->get('/')->assertSee('You do not have permission');
$this->setRestrictionsForTestRoles($page, ['view', 'update']);
- $this->visit($pageUrl . '/edit')
- ->seePageIs($pageUrl . '/edit')->seeInField('name', $page->name);
+ $this->get($pageUrl . '/edit')
+ ->assertOk()
+ ->assertElementExists('input[name="name"][value="' . $page->name . '"]');
}
public function test_page_delete_restriction()
{
- $page = Page::first();
+ /** @var Page $page */
+ $page = Page::query()->first();
$pageUrl = $page->getUrl();
$this->actingAs($this->user)
- ->visit($pageUrl . '/delete')
- ->see('Delete Page');
+ ->get($pageUrl . '/delete')
+ ->assertSee('Delete Page');
$this->setRestrictionsForTestRoles($page, ['view', 'update']);
- $this->forceVisit($pageUrl . '/delete')
- ->see('You do not have permission')->seePageIs('/');
+ $this->get($pageUrl . '/delete')->assertRedirect('/');
+ $this->get('/')->assertSee('You do not have permission');
$this->setRestrictionsForTestRoles($page, ['view', 'delete']);
- $this->visit($pageUrl . '/delete')
- ->seePageIs($pageUrl . '/delete')->see('Delete Page');
+ $this->get($pageUrl . '/delete')->assertOk()->assertSee('Delete Page');
+ }
+
+ protected function entityRestrictionFormTest(string $model, string $title, string $permission, string $roleId)
+ {
+ /** @var Entity $modelInstance */
+ $modelInstance = $model::query()->first();
+ $this->asAdmin()->get($modelInstance->getUrl('/permissions'))
+ ->assertSee($title);
+
+ $this->put($modelInstance->getUrl('/permissions'), [
+ 'restricted' => 'true',
+ 'restrictions' => [
+ $roleId => [
+ $permission => 'true',
+ ],
+ ],
+ ]);
+
+ $this->assertDatabaseHas($modelInstance->getTable(), ['id' => $modelInstance->id, 'restricted' => true]);
+ $this->assertDatabaseHas('entity_permissions', [
+ 'restrictable_id' => $modelInstance->id,
+ 'restrictable_type' => $modelInstance->getMorphClass(),
+ 'role_id' => $roleId,
+ 'action' => $permission,
+ ]);
}
public function test_bookshelf_restriction_form()
{
- $shelf = Bookshelf::first();
- $this->asAdmin()->visit($shelf->getUrl('/permissions'))
- ->see('Bookshelf Permissions')
- ->check('restricted')
- ->check('restrictions[2][view]')
- ->press('Save Permissions')
- ->seeInDatabase('bookshelves', ['id' => $shelf->id, 'restricted' => true])
- ->seeInDatabase('entity_permissions', [
- 'restrictable_id' => $shelf->id,
- 'restrictable_type' => Bookshelf::newModelInstance()->getMorphClass(),
- 'role_id' => '2',
- 'action' => 'view',
- ]);
+ $this->entityRestrictionFormTest(Bookshelf::class, 'Bookshelf Permissions', 'view', '2');
}
public function test_book_restriction_form()
{
- $book = Book::first();
- $this->asAdmin()->visit($book->getUrl() . '/permissions')
- ->see('Book Permissions')
- ->check('restricted')
- ->check('restrictions[2][view]')
- ->press('Save Permissions')
- ->seeInDatabase('books', ['id' => $book->id, 'restricted' => true])
- ->seeInDatabase('entity_permissions', [
- 'restrictable_id' => $book->id,
- 'restrictable_type' => Book::newModelInstance()->getMorphClass(),
- 'role_id' => '2',
- 'action' => 'view',
- ]);
+ $this->entityRestrictionFormTest(Book::class, 'Book Permissions', 'view', '2');
}
public function test_chapter_restriction_form()
{
- $chapter = Chapter::first();
- $this->asAdmin()->visit($chapter->getUrl() . '/permissions')
- ->see('Chapter Permissions')
- ->check('restricted')
- ->check('restrictions[2][update]')
- ->press('Save Permissions')
- ->seeInDatabase('chapters', ['id' => $chapter->id, 'restricted' => true])
- ->seeInDatabase('entity_permissions', [
- 'restrictable_id' => $chapter->id,
- 'restrictable_type' => Chapter::newModelInstance()->getMorphClass(),
- 'role_id' => '2',
- 'action' => 'update',
- ]);
+ $this->entityRestrictionFormTest(Chapter::class, 'Chapter Permissions', 'update', '2');
}
public function test_page_restriction_form()
{
- $page = Page::first();
- $this->asAdmin()->visit($page->getUrl() . '/permissions')
- ->see('Page Permissions')
- ->check('restricted')
- ->check('restrictions[2][delete]')
- ->press('Save Permissions')
- ->seeInDatabase('pages', ['id' => $page->id, 'restricted' => true])
- ->seeInDatabase('entity_permissions', [
- 'restrictable_id' => $page->id,
- 'restrictable_type' => Page::newModelInstance()->getMorphClass(),
- 'role_id' => '2',
- 'action' => 'delete',
- ]);
+ $this->entityRestrictionFormTest(Page::class, 'Page Permissions', 'delete', '2');
}
public function test_restricted_pages_not_visible_in_book_navigation_on_pages()
{
- $chapter = Chapter::first();
+ /** @var Chapter $chapter */
+ $chapter = Chapter::query()->first();
$page = $chapter->pages->first();
$page2 = $chapter->pages[2];
$this->setRestrictionsForTestRoles($page, []);
$this->actingAs($this->user)
- ->visit($page2->getUrl())
- ->dontSeeInElement('.sidebar-page-list', $page->name);
+ ->get($page2->getUrl())
+ ->assertElementNotContains('.sidebar-page-list', $page->name);
}
public function test_restricted_pages_not_visible_in_book_navigation_on_chapters()
{
- $chapter = Chapter::first();
+ /** @var Chapter $chapter */
+ $chapter = Chapter::query()->first();
$page = $chapter->pages->first();
$this->setRestrictionsForTestRoles($page, []);
$this->actingAs($this->user)
- ->visit($chapter->getUrl())
- ->dontSeeInElement('.sidebar-page-list', $page->name);
+ ->get($chapter->getUrl())
+ ->assertElementNotContains('.sidebar-page-list', $page->name);
}
public function test_restricted_pages_not_visible_on_chapter_pages()
{
- $chapter = Chapter::first();
+ /** @var Chapter $chapter */
+ $chapter = Chapter::query()->first();
$page = $chapter->pages->first();
$this->setRestrictionsForTestRoles($page, []);
$this->actingAs($this->user)
- ->visit($chapter->getUrl())
- ->dontSee($page->name);
+ ->get($chapter->getUrl())
+ ->assertDontSee($page->name);
}
public function test_restricted_chapter_pages_not_visible_on_book_page()
{
+ /** @var Chapter $chapter */
$chapter = Chapter::query()->first();
$this->actingAs($this->user)
- ->visit($chapter->book->getUrl())
- ->see($chapter->pages->first()->name);
+ ->get($chapter->book->getUrl())
+ ->assertSee($chapter->pages->first()->name);
foreach ($chapter->pages as $page) {
$this->setRestrictionsForTestRoles($page, []);
}
$this->actingAs($this->user)
- ->visit($chapter->book->getUrl())
- ->dontSee($chapter->pages->first()->name);
+ ->get($chapter->book->getUrl())
+ ->assertDontSee($chapter->pages->first()->name);
}
public function test_bookshelf_update_restriction_override()
{
- $shelf = Bookshelf::first();
+ /** @var Bookshelf $shelf */
+ $shelf = Bookshelf::query()->first();
$this->actingAs($this->viewer)
- ->visit($shelf->getUrl('/edit'))
- ->dontSee('Edit Book');
+ ->get($shelf->getUrl('/edit'))
+ ->assertDontSee('Edit Book');
$this->setRestrictionsForTestRoles($shelf, ['view', 'delete']);
- $this->forceVisit($shelf->getUrl('/edit'))
- ->see('You do not have permission')->seePageIs('/');
+ $this->get($shelf->getUrl('/edit'))->assertRedirect('/');
+ $this->get('/')->assertSee('You do not have permission');
$this->setRestrictionsForTestRoles($shelf, ['view', 'update']);
- $this->visit($shelf->getUrl('/edit'))
- ->seePageIs($shelf->getUrl('/edit'));
+ $this->get($shelf->getUrl('/edit'))->assertOk();
}
public function test_bookshelf_delete_restriction_override()
{
- $shelf = Bookshelf::first();
+ /** @var Bookshelf $shelf */
+ $shelf = Bookshelf::query()->first();
$this->actingAs($this->viewer)
- ->visit($shelf->getUrl('/delete'))
- ->dontSee('Delete Book');
+ ->get($shelf->getUrl('/delete'))
+ ->assertDontSee('Delete Book');
$this->setRestrictionsForTestRoles($shelf, ['view', 'update']);
- $this->forceVisit($shelf->getUrl('/delete'))
- ->see('You do not have permission')->seePageIs('/');
+ $this->get($shelf->getUrl('/delete'))->assertRedirect('/');
+ $this->get('/')->assertSee('You do not have permission');
$this->setRestrictionsForTestRoles($shelf, ['view', 'delete']);
- $this->visit($shelf->getUrl('/delete'))
- ->seePageIs($shelf->getUrl('/delete'))->see('Delete Book');
+ $this->get($shelf->getUrl('/delete'))->assertOk()->assertSee('Delete Book');
}
public function test_book_create_restriction_override()
{
- $book = Book::first();
+ /** @var Book $book */
+ $book = Book::query()->first();
$bookUrl = $book->getUrl();
$this->actingAs($this->viewer)
- ->visit($bookUrl)
- ->dontSeeInElement('.actions', 'New Page')
- ->dontSeeInElement('.actions', 'New Chapter');
+ ->get($bookUrl)
+ ->assertElementNotContains('.actions', 'New Page')
+ ->assertElementNotContains('.actions', 'New Chapter');
$this->setRestrictionsForTestRoles($book, ['view', 'delete', 'update']);
- $this->forceVisit($bookUrl . '/create-chapter')
- ->see('You do not have permission')->seePageIs('/');
- $this->forceVisit($bookUrl . '/create-page')
- ->see('You do not have permission')->seePageIs('/');
- $this->visit($bookUrl)->dontSeeInElement('.actions', 'New Page')
- ->dontSeeInElement('.actions', 'New Chapter');
+ $this->get($bookUrl . '/create-chapter')->assertRedirect('/');
+ $this->get('/')->assertSee('You do not have permission');
+ $this->get($bookUrl . '/create-page')->assertRedirect('/');
+ $this->get('/')->assertSee('You do not have permission');
+ $this->get($bookUrl)->assertElementNotContains('.actions', 'New Page')
+ ->assertElementNotContains('.actions', 'New Chapter');
$this->setRestrictionsForTestRoles($book, ['view', 'create']);
- $this->visit($bookUrl . '/create-chapter')
- ->type('test chapter', 'name')
- ->type('test description for chapter', 'description')
- ->press('Save Chapter')
- ->seePageIs($bookUrl . '/chapter/test-chapter');
- $this->visit($bookUrl . '/create-page')
- ->type('test page', 'name')
- ->type('test content', 'html')
- ->press('Save Page')
- ->seePageIs($bookUrl . '/page/test-page');
- $this->visit($bookUrl)->seeInElement('.actions', 'New Page')
- ->seeInElement('.actions', 'New Chapter');
+ $resp = $this->post($book->getUrl('/create-chapter'), [
+ 'name' => 'test chapter',
+ 'description' => 'test desc',
+ ]);
+ $resp->assertRedirect($book->getUrl('/chapter/test-chapter'));
+
+ $this->get($book->getUrl('/create-page'));
+ /** @var Page $page */
+ $page = Page::query()->where('draft', '=', true)->orderByDesc('id')->first();
+ $resp = $this->post($page->getUrl(), [
+ 'name' => 'test page',
+ 'html' => 'test desc',
+ ]);
+ $resp->assertRedirect($book->getUrl('/page/test-page'));
+
+ $this->get($bookUrl)
+ ->assertElementContains('.actions', 'New Page')
+ ->assertElementContains('.actions', 'New Chapter');
}
public function test_book_update_restriction_override()
{
- $book = Book::first();
+ /** @var Book $book */
+ $book = Book::query()->first();
$bookPage = $book->pages->first();
$bookChapter = $book->chapters->first();
$bookUrl = $book->getUrl();
- $this->actingAs($this->viewer)
- ->visit($bookUrl . '/edit')
- ->dontSee('Edit Book');
+ $this->actingAs($this->viewer)->get($bookUrl . '/edit')
+ ->assertDontSee('Edit Book');
$this->setRestrictionsForTestRoles($book, ['view', 'delete']);
- $this->forceVisit($bookUrl . '/edit')
- ->see('You do not have permission')->seePageIs('/');
- $this->forceVisit($bookPage->getUrl() . '/edit')
- ->see('You do not have permission')->seePageIs('/');
- $this->forceVisit($bookChapter->getUrl() . '/edit')
- ->see('You do not have permission')->seePageIs('/');
+ $this->get($bookUrl . '/edit')->assertRedirect('/');
+ $this->get('/')->assertSee('You do not have permission');
+ $this->get($bookPage->getUrl() . '/edit')->assertRedirect('/');
+ $this->get('/')->assertSee('You do not have permission');
+ $this->get($bookChapter->getUrl() . '/edit')->assertRedirect('/');
+ $this->get('/')->assertSee('You do not have permission');
$this->setRestrictionsForTestRoles($book, ['view', 'update']);
- $this->visit($bookUrl . '/edit')
- ->seePageIs($bookUrl . '/edit');
- $this->visit($bookPage->getUrl() . '/edit')
- ->seePageIs($bookPage->getUrl() . '/edit');
- $this->visit($bookChapter->getUrl() . '/edit')
- ->see('Edit Chapter');
+ $this->get($bookUrl . '/edit')->assertOk();
+ $this->get($bookPage->getUrl() . '/edit')->assertOk();
+ $this->get($bookChapter->getUrl() . '/edit')->assertSee('Edit Chapter');
}
public function test_book_delete_restriction_override()
{
- $book = Book::first();
+ /** @var Book $book */
+ $book = Book::query()->first();
$bookPage = $book->pages->first();
$bookChapter = $book->chapters->first();
$bookUrl = $book->getUrl();
$this->actingAs($this->viewer)
- ->visit($bookUrl . '/delete')
- ->dontSee('Delete Book');
+ ->get($bookUrl . '/delete')
+ ->assertDontSee('Delete Book');
$this->setRestrictionsForTestRoles($book, ['view', 'update']);
- $this->forceVisit($bookUrl . '/delete')
- ->see('You do not have permission')->seePageIs('/');
- $this->forceVisit($bookPage->getUrl() . '/delete')
- ->see('You do not have permission')->seePageIs('/');
- $this->forceVisit($bookChapter->getUrl() . '/delete')
- ->see('You do not have permission')->seePageIs('/');
+ $this->get($bookUrl . '/delete')->assertRedirect('/');
+ $this->get('/')->assertSee('You do not have permission');
+ $this->get($bookPage->getUrl() . '/delete')->assertRedirect('/');
+ $this->get('/')->assertSee('You do not have permission');
+ $this->get($bookChapter->getUrl() . '/delete')->assertRedirect('/');
+ $this->get('/')->assertSee('You do not have permission');
$this->setRestrictionsForTestRoles($book, ['view', 'delete']);
- $this->visit($bookUrl . '/delete')
- ->seePageIs($bookUrl . '/delete')->see('Delete Book');
- $this->visit($bookPage->getUrl() . '/delete')
- ->seePageIs($bookPage->getUrl() . '/delete')->see('Delete Page');
- $this->visit($bookChapter->getUrl() . '/delete')
- ->see('Delete Chapter');
+ $this->get($bookUrl . '/delete')->assertOk()->assertSee('Delete Book');
+ $this->get($bookPage->getUrl() . '/delete')->assertOk()->assertSee('Delete Page');
+ $this->get($bookChapter->getUrl() . '/delete')->assertSee('Delete Chapter');
}
public function test_page_visible_if_has_permissions_when_book_not_visible()
{
- $book = Book::first();
+ /** @var Book $book */
+ $book = Book::query()->first();
$bookChapter = $book->chapters->first();
$bookPage = $bookChapter->pages->first();
$this->setRestrictionsForTestRoles($bookPage, ['view']);
$this->actingAs($this->viewer);
- $this->get($bookPage->getUrl());
- $this->assertResponseOk();
- $this->see($bookPage->name);
- $this->dontSee(substr($book->name, 0, 15));
- $this->dontSee(substr($bookChapter->name, 0, 15));
+ $resp = $this->get($bookPage->getUrl());
+ $resp->assertOk();
+ $resp->assertSee($bookPage->name);
+ $resp->assertDontSee(substr($book->name, 0, 15));
+ $resp->assertDontSee(substr($bookChapter->name, 0, 15));
}
public function test_book_sort_view_permission()
{
- $firstBook = Book::first();
- $secondBook = Book::find(2);
+ /** @var Book $firstBook */
+ $firstBook = Book::query()->first();
+ /** @var Book $secondBook */
+ $secondBook = Book::query()->find(2);
$this->setRestrictionsForTestRoles($firstBook, ['view', 'update']);
$this->setRestrictionsForTestRoles($secondBook, ['view']);
// Test sort page visibility
- $this->actingAs($this->user)->visit($secondBook->getUrl() . '/sort')
- ->see('You do not have permission')
- ->seePageIs('/');
+ $this->actingAs($this->user)->get($secondBook->getUrl('/sort'))->assertRedirect('/');
+ $this->get('/')->assertSee('You do not have permission');
// Check sort page on first book
- $this->actingAs($this->user)->visit($firstBook->getUrl() . '/sort');
+ $this->actingAs($this->user)->get($firstBook->getUrl('/sort'));
}
public function test_book_sort_permission()
{
- $firstBook = Book::first();
- $secondBook = Book::find(2);
+ /** @var Book $firstBook */
+ $firstBook = Book::query()->first();
+ /** @var Book $secondBook */
+ $secondBook = Book::query()->find(2);
$this->setRestrictionsForTestRoles($firstBook, ['view', 'update']);
$this->setRestrictionsForTestRoles($secondBook, ['view']);
// Move chapter from first book to a second book
$this->actingAs($this->user)->put($firstBook->getUrl() . '/sort', ['sort-tree' => json_encode($reqData)])
- ->followRedirects()
- ->see('You do not have permission')
- ->seePageIs('/');
+ ->assertRedirect('/');
+ $this->get('/')->assertSee('You do not have permission');
$reqData = [
[
// Move chapter from second book to first book
$this->actingAs($this->user)->put($firstBook->getUrl() . '/sort', ['sort-tree' => json_encode($reqData)])
- ->followRedirects()
- ->see('You do not have permission')
- ->seePageIs('/');
+ ->assertRedirect('/');
+ $this->get('/')->assertSee('You do not have permission');
}
public function test_can_create_page_if_chapter_has_permissions_when_book_not_visible()
{
- $book = Book::first();
+ /** @var Book $book */
+ $book = Book::query()->first();
$this->setRestrictionsForTestRoles($book, []);
$bookChapter = $book->chapters->first();
$this->setRestrictionsForTestRoles($bookChapter, ['view']);
- $this->actingAs($this->user)->visit($bookChapter->getUrl())
- ->dontSee('New Page');
+ $this->actingAs($this->user)->get($bookChapter->getUrl())
+ ->assertDontSee('New Page');
$this->setRestrictionsForTestRoles($bookChapter, ['view', 'create']);
- $this->actingAs($this->user)->visit($bookChapter->getUrl())
- ->click('New Page')
- ->seeStatusCode(200)
- ->type('test page', 'name')
- ->type('test content', 'html')
- ->press('Save Page')
- ->seePageIs($book->getUrl('/page/test-page'))
- ->seeStatusCode(200);
+ $this->get($bookChapter->getUrl('/create-page'));
+ /** @var Page $page */
+ $page = Page::query()->where('draft', '=', true)->orderByDesc('id')->first();
+ $resp = $this->post($page->getUrl(), [
+ 'name' => 'test page',
+ 'html' => 'test content',
+ ]);
+ $resp->assertRedirect($book->getUrl('/page/test-page'));
}
}
namespace Tests\Permissions;
+use BookStack\Actions\ActivityType;
use BookStack\Actions\Comment;
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\Uploads\Image;
-use Laravel\BrowserKitTesting\HttpException;
-use Tests\BrowserKitTest;
+use Tests\TestCase;
+use Tests\TestResponse;
-class RolesTest extends BrowserKitTest
+class RolesTest extends TestCase
{
protected $user;
public function test_admin_can_see_settings()
{
- $this->asAdmin()->visit('/settings')->see('Settings');
+ $this->asAdmin()->get('/settings')->assertSee('Settings');
}
public function test_cannot_delete_admin_role()
{
$adminRole = Role::getRole('admin');
$deletePageUrl = '/settings/roles/delete/' . $adminRole->id;
- $this->asAdmin()->visit($deletePageUrl)
- ->press('Confirm')
- ->seePageIs($deletePageUrl)
- ->see('cannot be deleted');
+
+ $this->asAdmin()->get($deletePageUrl);
+ $this->delete($deletePageUrl)->assertRedirect($deletePageUrl);
+ $this->get($deletePageUrl)->assertSee('cannot be deleted');
}
public function test_role_cannot_be_deleted_if_default()
$this->setSettings(['registration-role' => $newRole->id]);
$deletePageUrl = '/settings/roles/delete/' . $newRole->id;
- $this->asAdmin()->visit($deletePageUrl)
- ->press('Confirm')
- ->seePageIs($deletePageUrl)
- ->see('cannot be deleted');
+ $this->asAdmin()->get($deletePageUrl);
+ $this->delete($deletePageUrl)->assertRedirect($deletePageUrl);
+ $this->get($deletePageUrl)->assertSee('cannot be deleted');
}
public function test_role_create_update_delete_flow()
$testRoleUpdateName = 'An Super Updated role';
// Creation
- $this->asAdmin()->visit('/settings')
- ->click('Roles')
- ->seePageIs('/settings/roles')
- ->click('Create New Role')
- ->type('Test Role', 'display_name')
- ->type('A little test description', 'description')
- ->press('Save Role')
- ->seeInDatabase('roles', ['display_name' => $testRoleName, 'description' => $testRoleDesc])
- ->seePageIs('/settings/roles');
+ $resp = $this->asAdmin()->get('/settings');
+ $resp->assertElementContains('a[href="' . url('/settings/roles') . '"]', 'Roles');
+
+ $resp = $this->get('/settings/roles');
+ $resp->assertElementContains('a[href="' . url('/settings/roles/new') . '"]', 'Create New Role');
+
+ $resp = $this->get('/settings/roles/new');
+ $resp->assertElementContains('form[action="' . url('/settings/roles/new') . '"]', 'Save Role');
+
+ $resp = $this->post('/settings/roles/new', [
+ 'display_name' => $testRoleName,
+ 'description' => $testRoleDesc,
+ ]);
+ $resp->assertRedirect('/settings/roles');
+
+ $resp = $this->get('/settings/roles');
+ $resp->assertSee($testRoleName);
+ $resp->assertSee($testRoleDesc);
+ $this->assertDatabaseHas('roles', [
+ 'display_name' => $testRoleName,
+ 'description' => $testRoleDesc,
+ 'mfa_enforced' => false,
+ ]);
+
+ /** @var Role $role */
+ $role = Role::query()->where('display_name', '=', $testRoleName)->first();
+
// Updating
- $this->asAdmin()->visit('/settings/roles')
- ->see($testRoleDesc)
- ->click($testRoleName)
- ->type($testRoleUpdateName, '#display_name')
- ->press('Save Role')
- ->seeInDatabase('roles', ['display_name' => $testRoleUpdateName, 'description' => $testRoleDesc])
- ->seePageIs('/settings/roles');
+ $resp = $this->get('/settings/roles/' . $role->id);
+ $resp->assertSee($testRoleName);
+ $resp->assertSee($testRoleDesc);
+ $resp->assertElementContains('form[action="' . url('/settings/roles/' . $role->id) . '"]', 'Save Role');
+
+ $resp = $this->put('/settings/roles/' . $role->id, [
+ 'display_name' => $testRoleUpdateName,
+ 'description' => $testRoleDesc,
+ 'mfa_enforced' => 'true',
+ ]);
+ $resp->assertRedirect('/settings/roles');
+ $this->assertDatabaseHas('roles', [
+ 'display_name' => $testRoleUpdateName,
+ 'description' => $testRoleDesc,
+ 'mfa_enforced' => true,
+ ]);
+
// Deleting
- $this->asAdmin()->visit('/settings/roles')
- ->click($testRoleUpdateName)
- ->click('Delete Role')
- ->see($testRoleUpdateName)
- ->press('Confirm')
- ->seePageIs('/settings/roles')
- ->dontSee($testRoleUpdateName);
+ $resp = $this->get('/settings/roles/' . $role->id);
+ $resp->assertElementContains('a[href="' . url("/settings/roles/delete/$role->id") . '"]', 'Delete Role');
+
+ $resp = $this->get("/settings/roles/delete/$role->id");
+ $resp->assertSee($testRoleUpdateName);
+ $resp->assertElementContains('form[action="' . url("/settings/roles/delete/$role->id") . '"]', 'Confirm');
+
+ $resp = $this->delete("/settings/roles/delete/$role->id");
+ $resp->assertRedirect('/settings/roles');
+ $this->get('/settings/roles')->assertSee('Role successfully deleted');
+ $this->assertActivityExists(ActivityType::ROLE_DELETE);
}
- public function test_admin_role_cannot_be_removed_if_last_admin()
+ public function test_admin_role_cannot_be_removed_if_user_last_admin()
{
- $adminRole = Role::where('system_name', '=', 'admin')->first();
+ /** @var Role $adminRole */
+ $adminRole = Role::query()->where('system_name', '=', 'admin')->first();
$adminUser = $this->getAdmin();
$adminRole->users()->where('id', '!=', $adminUser->id)->delete();
- $this->assertEquals($adminRole->users()->count(), 1);
+ $this->assertEquals(1, $adminRole->users()->count());
$viewerRole = $this->getViewer()->roles()->first();
$editUrl = '/settings/users/' . $adminUser->id;
- $this->actingAs($adminUser)->put($editUrl, [
+ $resp = $this->actingAs($adminUser)->put($editUrl, [
'name' => $adminUser->name,
'email' => $adminUser->email,
'roles' => [
'viewer' => strval($viewerRole->id),
],
- ])->followRedirects();
+ ]);
+
+ $resp->assertRedirect($editUrl);
- $this->seePageIs($editUrl);
- $this->see('This user is the only user assigned to the administrator role');
+ $resp = $this->get($editUrl);
+ $resp->assertSee('This user is the only user assigned to the administrator role');
}
public function test_migrate_users_on_delete_works()
{
+ /** @var Role $roleA */
$roleA = Role::query()->create(['display_name' => 'Delete Test A']);
+ /** @var Role $roleB */
$roleB = Role::query()->create(['display_name' => 'Delete Test B']);
$this->user->attachRole($roleB);
$this->assertCount(0, $roleA->users()->get());
$this->assertCount(1, $roleB->users()->get());
- $deletePage = $this->asAdmin()->get("/settings/roles/delete/{$roleB->id}");
- $deletePage->seeElement('select[name=migrate_role_id]');
- $this->asAdmin()->delete("/settings/roles/delete/{$roleB->id}", [
+ $deletePage = $this->asAdmin()->get("/settings/roles/delete/$roleB->id");
+ $deletePage->assertElementExists('select[name=migrate_role_id]');
+ $this->asAdmin()->delete("/settings/roles/delete/$roleB->id", [
'migrate_role_id' => $roleA->id,
]);
public function test_manage_user_permission()
{
- $this->actingAs($this->user)->visit('/settings/users')
- ->seePageIs('/');
+ $this->actingAs($this->user)->get('/settings/users')->assertRedirect('/');
$this->giveUserPermissions($this->user, ['users-manage']);
- $this->actingAs($this->user)->visit('/settings/users')
- ->seePageIs('/settings/users');
+ $this->actingAs($this->user)->get('/settings/users')->assertOk();
}
public function test_manage_users_permission_shows_link_in_header_if_does_not_have_settings_manage_permision()
{
$usersLink = 'href="' . url('/settings/users') . '"';
- $this->actingAs($this->user)->visit('/')->dontSee($usersLink);
+ $this->actingAs($this->user)->get('/')->assertDontSee($usersLink);
$this->giveUserPermissions($this->user, ['users-manage']);
- $this->actingAs($this->user)->visit('/')->see($usersLink);
+ $this->actingAs($this->user)->get('/')->assertSee($usersLink);
$this->giveUserPermissions($this->user, ['settings-manage', 'users-manage']);
- $this->actingAs($this->user)->visit('/')->dontSee($usersLink);
+ $this->actingAs($this->user)->get('/')->assertDontSee($usersLink);
}
public function test_user_cannot_change_email_unless_they_have_manage_users_permission()
$originalEmail = $this->user->email;
$this->actingAs($this->user);
- $this->visit($userProfileUrl)
- ->assertResponseOk()
- ->seeElement('input[name=email][disabled]');
+ $this->get($userProfileUrl)
+ ->assertOk()
+ ->assertElementExists('input[name=email][disabled]');
$this->put($userProfileUrl, [
'name' => 'my_new_name',
]);
- $this->seeInDatabase('users', [
+ $this->assertDatabaseHas('users', [
'id' => $this->user->id,
'email' => $originalEmail,
'name' => 'my_new_name',
$this->giveUserPermissions($this->user, ['users-manage']);
- $this->visit($userProfileUrl)
- ->assertResponseOk()
- ->dontSeeElement('input[name=email][disabled]')
- ->seeElement('input[name=email]');
+ $this->get($userProfileUrl)
+ ->assertOk()
+ ->assertElementNotExists('input[name=email][disabled]')
+ ->assertElementExists('input[name=email]');
$this->put($userProfileUrl, [
'name' => 'my_new_name_2',
]);
- $this->seeInDatabase('users', [
+ $this->assertDatabaseHas('users', [
'id' => $this->user->id,
'name' => 'my_new_name_2',
public function test_user_roles_manage_permission()
{
- $this->actingAs($this->user)->visit('/settings/roles')
- ->seePageIs('/')->visit('/settings/roles/1')->seePageIs('/');
+ $this->actingAs($this->user)->get('/settings/roles')->assertRedirect('/');
+ $this->get('/settings/roles/1')->assertRedirect('/');
$this->giveUserPermissions($this->user, ['user-roles-manage']);
- $this->actingAs($this->user)->visit('/settings/roles')
- ->seePageIs('/settings/roles')->click('Admin')
- ->see('Edit Role');
+ $this->actingAs($this->user)->get('/settings/roles')->assertOk();
+ $this->get('/settings/roles/1')
+ ->assertOk()
+ ->assertSee('Admin');
}
public function test_settings_manage_permission()
{
- $this->actingAs($this->user)->visit('/settings')
- ->seePageIs('/');
+ $this->actingAs($this->user)->get('/settings')->assertRedirect('/');
$this->giveUserPermissions($this->user, ['settings-manage']);
- $this->actingAs($this->user)->visit('/settings')
- ->seePageIs('/settings')->press('Save Settings')->see('Settings Saved');
+ $this->get('/settings')->assertOk();
+
+ $resp = $this->post('/settings', []);
+ $resp->assertRedirect('/settings');
+ $resp = $this->get('/settings');
+ $resp->assertSee('Settings saved');
}
public function test_restrictions_manage_all_permission()
{
- $page = Page::take(1)->get()->first();
- $this->actingAs($this->user)->visit($page->getUrl())
- ->dontSee('Permissions')
- ->visit($page->getUrl() . '/permissions')
- ->seePageIs('/');
+ $page = Page::query()->get()->first();
+
+ $this->actingAs($this->user)->get($page->getUrl())->assertDontSee('Permissions');
+ $this->get($page->getUrl('/permissions'))->assertRedirect('/');
+
$this->giveUserPermissions($this->user, ['restrictions-manage-all']);
- $this->actingAs($this->user)->visit($page->getUrl())
- ->see('Permissions')
- ->click('Permissions')
- ->see('Page Permissions')->seePageIs($page->getUrl() . '/permissions');
+
+ $this->actingAs($this->user)->get($page->getUrl())->assertSee('Permissions');
+
+ $this->get($page->getUrl('/permissions'))
+ ->assertOk()
+ ->assertSee('Page Permissions');
}
public function test_restrictions_manage_own_permission()
{
- $otherUsersPage = Page::first();
+ /** @var Page $otherUsersPage */
+ $otherUsersPage = Page::query()->first();
$content = $this->createEntityChainBelongingToUser($this->user);
// Set a different creator on the page we're checking to ensure
$page->save();
// Check can't restrict other's content
- $this->actingAs($this->user)->visit($otherUsersPage->getUrl())
- ->dontSee('Permissions')
- ->visit($otherUsersPage->getUrl() . '/permissions')
- ->seePageIs('/');
+ $this->actingAs($this->user)->get($otherUsersPage->getUrl())->assertDontSee('Permissions');
+ $this->get($otherUsersPage->getUrl('/permissions'))->assertRedirect('/');
+
// Check can't restrict own content
- $this->actingAs($this->user)->visit($page->getUrl())
- ->dontSee('Permissions')
- ->visit($page->getUrl() . '/permissions')
- ->seePageIs('/');
+ $this->actingAs($this->user)->get($page->getUrl())->assertDontSee('Permissions');
+ $this->get($page->getUrl('/permissions'))->assertRedirect('/');
$this->giveUserPermissions($this->user, ['restrictions-manage-own']);
// Check can't restrict other's content
- $this->actingAs($this->user)->visit($otherUsersPage->getUrl())
- ->dontSee('Permissions')
- ->visit($otherUsersPage->getUrl() . '/permissions')
- ->seePageIs('/');
+ $this->actingAs($this->user)->get($otherUsersPage->getUrl())->assertDontSee('Permissions');
+ $this->get($otherUsersPage->getUrl('/permissions'))->assertRedirect();
+
// Check can restrict own content
- $this->actingAs($this->user)->visit($page->getUrl())
- ->see('Permissions')
- ->click('Permissions')
- ->seePageIs($page->getUrl() . '/permissions');
+ $this->actingAs($this->user)->get($page->getUrl())->assertSee('Permissions');
+ $this->get($page->getUrl('/permissions'))->assertOk();
}
/**
* Check a standard entity access permission.
- *
- * @param string $permission
- * @param array $accessUrls Urls that are only accessible after having the permission
- * @param array $visibles Check this text, In the buttons toolbar, is only visible with the permission
*/
- private function checkAccessPermission($permission, $accessUrls = [], $visibles = [])
+ private function checkAccessPermission(string $permission, array $accessUrls = [], array $visibles = [])
{
foreach ($accessUrls as $url) {
- $this->actingAs($this->user)->visit($url)
- ->seePageIs('/');
+ $this->actingAs($this->user)->get($url)->assertRedirect('/');
}
+
foreach ($visibles as $url => $text) {
- $this->actingAs($this->user)->visit($url)
- ->dontSeeInElement('.action-buttons', $text);
+ $this->actingAs($this->user)->get($url)
+ ->assertElementNotContains('.action-buttons', $text);
}
$this->giveUserPermissions($this->user, [$permission]);
foreach ($accessUrls as $url) {
- $this->actingAs($this->user)->visit($url)
- ->seePageIs($url);
+ $this->actingAs($this->user)->get($url)->assertOk();
}
foreach ($visibles as $url => $text) {
- $this->actingAs($this->user)->visit($url)
- ->see($text);
+ $this->actingAs($this->user)->get($url)->assertSee($text);
}
}
'/shelves' => 'New Shelf',
]);
- $this->visit('/create-shelf')
- ->type('test shelf', 'name')
- ->type('shelf desc', 'description')
- ->press('Save Shelf')
- ->seePageIs('/shelves/test-shelf');
+ $this->post('/shelves', [
+ 'name' => 'test shelf',
+ 'description' => 'shelf desc',
+ ])->assertRedirect('/shelves/test-shelf');
}
public function test_bookshelves_edit_own_permission()
{
- $otherShelf = Bookshelf::first();
+ /** @var Bookshelf $otherShelf */
+ $otherShelf = Bookshelf::query()->first();
$ownShelf = $this->newShelf(['name' => 'test-shelf', 'slug' => 'test-shelf']);
$ownShelf->forceFill(['owned_by' => $this->user->id, 'updated_by' => $this->user->id])->save();
$this->regenEntityPermissions($ownShelf);
$ownShelf->getUrl() => 'Edit',
]);
- $this->visit($otherShelf->getUrl())
- ->dontSeeInElement('.action-buttons', 'Edit')
- ->visit($otherShelf->getUrl('/edit'))
- ->seePageIs('/');
+ $this->get($otherShelf->getUrl())->assertElementNotContains('.action-buttons', 'Edit');
+ $this->get($otherShelf->getUrl('/edit'))->assertRedirect('/');
}
public function test_bookshelves_edit_all_permission()
{
- $otherShelf = Bookshelf::first();
+ /** @var Bookshelf $otherShelf */
+ $otherShelf = Bookshelf::query()->first();
$this->checkAccessPermission('bookshelf-update-all', [
$otherShelf->getUrl('/edit'),
], [
public function test_bookshelves_delete_own_permission()
{
$this->giveUserPermissions($this->user, ['bookshelf-update-all']);
- $otherShelf = Bookshelf::first();
+ /** @var Bookshelf $otherShelf */
+ $otherShelf = Bookshelf::query()->first();
$ownShelf = $this->newShelf(['name' => 'test-shelf', 'slug' => 'test-shelf']);
$ownShelf->forceFill(['owned_by' => $this->user->id, 'updated_by' => $this->user->id])->save();
$this->regenEntityPermissions($ownShelf);
$ownShelf->getUrl() => 'Delete',
]);
- $this->visit($otherShelf->getUrl())
- ->dontSeeInElement('.action-buttons', 'Delete')
- ->visit($otherShelf->getUrl('/delete'))
- ->seePageIs('/');
- $this->visit($ownShelf->getUrl())->visit($ownShelf->getUrl('/delete'))
- ->press('Confirm')
- ->seePageIs('/shelves')
- ->dontSee($ownShelf->name);
+ $this->get($otherShelf->getUrl())->assertElementNotContains('.action-buttons', 'Delete');
+ $this->get($otherShelf->getUrl('/delete'))->assertRedirect('/');
+
+ $this->get($ownShelf->getUrl());
+ $this->delete($ownShelf->getUrl())->assertRedirect('/shelves');
+ $this->get('/shelves')->assertDontSee($ownShelf->name);
}
public function test_bookshelves_delete_all_permission()
{
$this->giveUserPermissions($this->user, ['bookshelf-update-all']);
- $otherShelf = Bookshelf::first();
+ /** @var Bookshelf $otherShelf */
+ $otherShelf = Bookshelf::query()->first();
$this->checkAccessPermission('bookshelf-delete-all', [
$otherShelf->getUrl('/delete'),
], [
$otherShelf->getUrl() => 'Delete',
]);
- $this->visit($otherShelf->getUrl())->visit($otherShelf->getUrl('/delete'))
- ->press('Confirm')
- ->seePageIs('/shelves')
- ->dontSee($otherShelf->name);
+ $this->delete($otherShelf->getUrl())->assertRedirect('/shelves');
+ $this->get('/shelves')->assertDontSee($otherShelf->name);
}
public function test_books_create_all_permissions()
'/books' => 'Create New Book',
]);
- $this->visit('/create-book')
- ->type('test book', 'name')
- ->type('book desc', 'description')
- ->press('Save Book')
- ->seePageIs('/books/test-book');
+ $this->post('/books', [
+ 'name' => 'test book',
+ 'description' => 'book desc',
+ ])->assertRedirect('/books/test-book');
}
public function test_books_edit_own_permission()
{
- $otherBook = Book::take(1)->get()->first();
+ /** @var Book $otherBook */
+ $otherBook = Book::query()->take(1)->get()->first();
$ownBook = $this->createEntityChainBelongingToUser($this->user)['book'];
$this->checkAccessPermission('book-update-own', [
$ownBook->getUrl() . '/edit',
$ownBook->getUrl() => 'Edit',
]);
- $this->visit($otherBook->getUrl())
- ->dontSeeInElement('.action-buttons', 'Edit')
- ->visit($otherBook->getUrl() . '/edit')
- ->seePageIs('/');
+ $this->get($otherBook->getUrl())->assertElementNotContains('.action-buttons', 'Edit');
+ $this->get($otherBook->getUrl('/edit'))->assertRedirect('/');
}
public function test_books_edit_all_permission()
{
- $otherBook = Book::take(1)->get()->first();
+ /** @var Book $otherBook */
+ $otherBook = Book::query()->take(1)->get()->first();
$this->checkAccessPermission('book-update-all', [
$otherBook->getUrl() . '/edit',
], [
public function test_books_delete_own_permission()
{
$this->giveUserPermissions($this->user, ['book-update-all']);
- $otherBook = Book::take(1)->get()->first();
+ /** @var Book $otherBook */
+ $otherBook = Book::query()->take(1)->get()->first();
$ownBook = $this->createEntityChainBelongingToUser($this->user)['book'];
$this->checkAccessPermission('book-delete-own', [
$ownBook->getUrl() . '/delete',
$ownBook->getUrl() => 'Delete',
]);
- $this->visit($otherBook->getUrl())
- ->dontSeeInElement('.action-buttons', 'Delete')
- ->visit($otherBook->getUrl() . '/delete')
- ->seePageIs('/');
- $this->visit($ownBook->getUrl())->visit($ownBook->getUrl() . '/delete')
- ->press('Confirm')
- ->seePageIs('/books')
- ->dontSee($ownBook->name);
+ $this->get($otherBook->getUrl())->assertElementNotContains('.action-buttons', 'Delete');
+ $this->get($otherBook->getUrl('/delete'))->assertRedirect('/');
+ $this->get($ownBook->getUrl());
+ $this->delete($ownBook->getUrl())->assertRedirect('/books');
+ $this->get('/books')->assertDontSee($ownBook->name);
}
public function test_books_delete_all_permission()
{
$this->giveUserPermissions($this->user, ['book-update-all']);
- $otherBook = Book::take(1)->get()->first();
+ /** @var Book $otherBook */
+ $otherBook = Book::query()->take(1)->get()->first();
$this->checkAccessPermission('book-delete-all', [
$otherBook->getUrl() . '/delete',
], [
$otherBook->getUrl() => 'Delete',
]);
- $this->visit($otherBook->getUrl())->visit($otherBook->getUrl() . '/delete')
- ->press('Confirm')
- ->seePageIs('/books')
- ->dontSee($otherBook->name);
+ $this->get($otherBook->getUrl());
+ $this->delete($otherBook->getUrl())->assertRedirect('/books');
+ $this->get('/books')->assertDontSee($otherBook->name);
}
public function test_chapter_create_own_permissions()
{
- $book = Book::take(1)->get()->first();
+ /** @var Book $book */
+ $book = Book::query()->take(1)->get()->first();
$ownBook = $this->createEntityChainBelongingToUser($this->user)['book'];
$this->checkAccessPermission('chapter-create-own', [
$ownBook->getUrl('/create-chapter'),
$ownBook->getUrl() => 'New Chapter',
]);
- $this->visit($ownBook->getUrl('/create-chapter'))
- ->type('test chapter', 'name')
- ->type('chapter desc', 'description')
- ->press('Save Chapter')
- ->seePageIs($ownBook->getUrl('/chapter/test-chapter'));
+ $this->post($ownBook->getUrl('/create-chapter'), [
+ 'name' => 'test chapter',
+ 'description' => 'chapter desc',
+ ])->assertRedirect($ownBook->getUrl('/chapter/test-chapter'));
- $this->visit($book->getUrl())
- ->dontSeeInElement('.action-buttons', 'New Chapter')
- ->visit($book->getUrl('/create-chapter'))
- ->seePageIs('/');
+ $this->get($book->getUrl())->assertElementNotContains('.action-buttons', 'New Chapter');
+ $this->get($book->getUrl('/create-chapter'))->assertRedirect('/');
}
public function test_chapter_create_all_permissions()
{
- $book = Book::take(1)->get()->first();
+ /** @var Book $book */
+ $book = Book::query()->first();
$this->checkAccessPermission('chapter-create-all', [
$book->getUrl('/create-chapter'),
], [
$book->getUrl() => 'New Chapter',
]);
- $this->visit($book->getUrl('/create-chapter'))
- ->type('test chapter', 'name')
- ->type('chapter desc', 'description')
- ->press('Save Chapter')
- ->seePageIs($book->getUrl('/chapter/test-chapter'));
+ $this->post($book->getUrl('/create-chapter'), [
+ 'name' => 'test chapter',
+ 'description' => 'chapter desc',
+ ])->assertRedirect($book->getUrl('/chapter/test-chapter'));
}
public function test_chapter_edit_own_permission()
{
- $otherChapter = Chapter::take(1)->get()->first();
+ /** @var Chapter $otherChapter */
+ $otherChapter = Chapter::query()->first();
$ownChapter = $this->createEntityChainBelongingToUser($this->user)['chapter'];
$this->checkAccessPermission('chapter-update-own', [
$ownChapter->getUrl() . '/edit',
$ownChapter->getUrl() => 'Edit',
]);
- $this->visit($otherChapter->getUrl())
- ->dontSeeInElement('.action-buttons', 'Edit')
- ->visit($otherChapter->getUrl() . '/edit')
- ->seePageIs('/');
+ $this->get($otherChapter->getUrl())->assertElementNotContains('.action-buttons', 'Edit');
+ $this->get($otherChapter->getUrl('/edit'))->assertRedirect('/');
}
public function test_chapter_edit_all_permission()
{
- $otherChapter = Chapter::take(1)->get()->first();
+ /** @var Chapter $otherChapter */
+ $otherChapter = Chapter::query()->take(1)->get()->first();
$this->checkAccessPermission('chapter-update-all', [
$otherChapter->getUrl() . '/edit',
], [
public function test_chapter_delete_own_permission()
{
$this->giveUserPermissions($this->user, ['chapter-update-all']);
- $otherChapter = Chapter::take(1)->get()->first();
+ /** @var Chapter $otherChapter */
+ $otherChapter = Chapter::query()->first();
$ownChapter = $this->createEntityChainBelongingToUser($this->user)['chapter'];
$this->checkAccessPermission('chapter-delete-own', [
$ownChapter->getUrl() . '/delete',
]);
$bookUrl = $ownChapter->book->getUrl();
- $this->visit($otherChapter->getUrl())
- ->dontSeeInElement('.action-buttons', 'Delete')
- ->visit($otherChapter->getUrl() . '/delete')
- ->seePageIs('/');
- $this->visit($ownChapter->getUrl())->visit($ownChapter->getUrl() . '/delete')
- ->press('Confirm')
- ->seePageIs($bookUrl)
- ->dontSeeInElement('.book-content', $ownChapter->name);
+ $this->get($otherChapter->getUrl())->assertElementNotContains('.action-buttons', 'Delete');
+ $this->get($otherChapter->getUrl('/delete'))->assertRedirect('/');
+ $this->get($ownChapter->getUrl());
+ $this->delete($ownChapter->getUrl())->assertRedirect($bookUrl);
+ $this->get($bookUrl)->assertElementNotContains('.book-content', $ownChapter->name);
}
public function test_chapter_delete_all_permission()
{
$this->giveUserPermissions($this->user, ['chapter-update-all']);
- $otherChapter = Chapter::take(1)->get()->first();
+ /** @var Chapter $otherChapter */
+ $otherChapter = Chapter::query()->first();
$this->checkAccessPermission('chapter-delete-all', [
$otherChapter->getUrl() . '/delete',
], [
]);
$bookUrl = $otherChapter->book->getUrl();
- $this->visit($otherChapter->getUrl())->visit($otherChapter->getUrl() . '/delete')
- ->press('Confirm')
- ->seePageIs($bookUrl)
- ->dontSeeInElement('.book-content', $otherChapter->name);
+ $this->get($otherChapter->getUrl());
+ $this->delete($otherChapter->getUrl())->assertRedirect($bookUrl);
+ $this->get($bookUrl)->assertElementNotContains('.book-content', $otherChapter->name);
}
public function test_page_create_own_permissions()
{
- $book = Book::first();
- $chapter = Chapter::first();
+ /** @var Book $book */
+ $book = Book::query()->first();
+ /** @var Chapter $chapter */
+ $chapter = Chapter::query()->first();
$entities = $this->createEntityChainBelongingToUser($this->user);
$ownBook = $entities['book'];
$accessUrls = [$createUrl, $createUrlChapter];
foreach ($accessUrls as $url) {
- $this->actingAs($this->user)->visit($url)
- ->seePageIs('/');
+ $this->actingAs($this->user)->get($url)->assertRedirect('/');
}
$this->checkAccessPermission('page-create-own', [], [
$this->giveUserPermissions($this->user, ['page-create-own']);
foreach ($accessUrls as $index => $url) {
- $this->actingAs($this->user)->visit($url);
- $expectedUrl = Page::where('draft', '=', true)->orderBy('id', 'desc')->first()->getUrl();
- $this->seePageIs($expectedUrl);
+ $resp = $this->actingAs($this->user)->get($url);
+ $expectedUrl = Page::query()->where('draft', '=', true)->orderBy('id', 'desc')->first()->getUrl();
+ $resp->assertRedirect($expectedUrl);
}
- $this->visit($createUrl)
- ->type('test page', 'name')
- ->type('page desc', 'html')
- ->press('Save Page')
- ->seePageIs($ownBook->getUrl('/page/test-page'));
+ $this->get($createUrl);
+ /** @var Page $draft */
+ $draft = Page::query()->where('draft', '=', true)->orderBy('id', 'desc')->first();
+ $this->post($draft->getUrl(), [
+ 'name' => 'test page',
+ 'html' => 'page desc',
+ ])->assertRedirect($ownBook->getUrl('/page/test-page'));
+
+ $this->get($book->getUrl())->assertElementNotContains('.action-buttons', 'New Page');
+ $this->get($book->getUrl('/create-page'))->assertRedirect('/');
- $this->visit($book->getUrl())
- ->dontSeeInElement('.action-buttons', 'New Page')
- ->visit($book->getUrl() . '/create-page')
- ->seePageIs('/');
- $this->visit($chapter->getUrl())
- ->dontSeeInElement('.action-buttons', 'New Page')
- ->visit($chapter->getUrl() . '/create-page')
- ->seePageIs('/');
+ $this->get($chapter->getUrl())->assertElementNotContains('.action-buttons', 'New Page');
+ $this->get($chapter->getUrl('/create-page'))->assertRedirect('/');
}
public function test_page_create_all_permissions()
{
- $book = Book::take(1)->get()->first();
- $chapter = Chapter::take(1)->get()->first();
- $baseUrl = $book->getUrl() . '/page';
+ /** @var Book $book */
+ $book = Book::query()->first();
+ /** @var Chapter $chapter */
+ $chapter = Chapter::query()->first();
$createUrl = $book->getUrl('/create-page');
$createUrlChapter = $chapter->getUrl('/create-page');
$accessUrls = [$createUrl, $createUrlChapter];
foreach ($accessUrls as $url) {
- $this->actingAs($this->user)->visit($url)
- ->seePageIs('/');
+ $this->actingAs($this->user)->get($url)->assertRedirect('/');
}
$this->checkAccessPermission('page-create-all', [], [
$this->giveUserPermissions($this->user, ['page-create-all']);
foreach ($accessUrls as $index => $url) {
- $this->actingAs($this->user)->visit($url);
- $expectedUrl = Page::where('draft', '=', true)->orderBy('id', 'desc')->first()->getUrl();
- $this->seePageIs($expectedUrl);
+ $resp = $this->actingAs($this->user)->get($url);
+ $expectedUrl = Page::query()->where('draft', '=', true)->orderBy('id', 'desc')->first()->getUrl();
+ $resp->assertRedirect($expectedUrl);
}
- $this->visit($createUrl)
- ->type('test page', 'name')
- ->type('page desc', 'html')
- ->press('Save Page')
- ->seePageIs($book->getUrl('/page/test-page'));
+ $this->get($createUrl);
+ /** @var Page $draft */
+ $draft = Page::query()->where('draft', '=', true)->orderByDesc('id')->first();
+ $this->post($draft->getUrl(), [
+ 'name' => 'test page',
+ 'html' => 'page desc',
+ ])->assertRedirect($book->getUrl('/page/test-page'));
- $this->visit($chapter->getUrl('/create-page'))
- ->type('new test page', 'name')
- ->type('page desc', 'html')
- ->press('Save Page')
- ->seePageIs($book->getUrl('/page/new-test-page'));
+ $this->get($chapter->getUrl('/create-page'));
+ /** @var Page $draft */
+ $draft = Page::query()->where('draft', '=', true)->orderByDesc('id')->first();
+ $this->post($draft->getUrl(), [
+ 'name' => 'new test page',
+ 'html' => 'page desc',
+ ])->assertRedirect($book->getUrl('/page/new-test-page'));
}
public function test_page_edit_own_permission()
{
- $otherPage = Page::take(1)->get()->first();
+ /** @var Page $otherPage */
+ $otherPage = Page::query()->first();
$ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
$this->checkAccessPermission('page-update-own', [
$ownPage->getUrl() . '/edit',
$ownPage->getUrl() => 'Edit',
]);
- $this->visit($otherPage->getUrl())
- ->dontSeeInElement('.action-buttons', 'Edit')
- ->visit($otherPage->getUrl() . '/edit')
- ->seePageIs('/');
+ $this->get($otherPage->getUrl())->assertElementNotContains('.action-buttons', 'Edit');
+ $this->get($otherPage->getUrl() . '/edit')->assertRedirect('/');
}
public function test_page_edit_all_permission()
{
- $otherPage = Page::take(1)->get()->first();
+ /** @var Page $otherPage */
+ $otherPage = Page::query()->first();
$this->checkAccessPermission('page-update-all', [
- $otherPage->getUrl() . '/edit',
+ $otherPage->getUrl('/edit'),
], [
$otherPage->getUrl() => 'Edit',
]);
public function test_page_delete_own_permission()
{
$this->giveUserPermissions($this->user, ['page-update-all']);
- $otherPage = Page::take(1)->get()->first();
+ /** @var Page $otherPage */
+ $otherPage = Page::query()->first();
$ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
$this->checkAccessPermission('page-delete-own', [
$ownPage->getUrl() . '/delete',
]);
$parent = $ownPage->chapter ?? $ownPage->book;
- $this->visit($otherPage->getUrl())
- ->dontSeeInElement('.action-buttons', 'Delete')
- ->visit($otherPage->getUrl() . '/delete')
- ->seePageIs('/');
- $this->visit($ownPage->getUrl())->visit($ownPage->getUrl() . '/delete')
- ->press('Confirm')
- ->seePageIs($parent->getUrl())
- ->dontSeeInElement('.book-content', $ownPage->name);
+ $this->get($otherPage->getUrl())->assertElementNotContains('.action-buttons', 'Delete');
+ $this->get($otherPage->getUrl('/delete'))->assertRedirect('/');
+ $this->get($ownPage->getUrl());
+ $this->delete($ownPage->getUrl())->assertRedirect($parent->getUrl());
+ $this->get($parent->getUrl())->assertElementNotContains('.book-content', $ownPage->name);
}
public function test_page_delete_all_permission()
{
$this->giveUserPermissions($this->user, ['page-update-all']);
- $otherPage = Page::take(1)->get()->first();
+ /** @var Page $otherPage */
+ $otherPage = Page::query()->first();
+
$this->checkAccessPermission('page-delete-all', [
$otherPage->getUrl() . '/delete',
], [
$otherPage->getUrl() => 'Delete',
]);
+ /** @var Entity $parent */
$parent = $otherPage->chapter ?? $otherPage->book;
- $this->visit($otherPage->getUrl())->visit($otherPage->getUrl() . '/delete')
- ->press('Confirm')
- ->seePageIs($parent->getUrl())
- ->dontSeeInElement('.book-content', $otherPage->name);
+ $this->get($otherPage->getUrl());
+
+ $this->delete($otherPage->getUrl())->assertRedirect($parent->getUrl());
+ $this->get($parent->getUrl())->assertDontSee($otherPage->name);
}
public function test_public_role_visible_in_user_edit_screen()
{
- $user = User::first();
+ /** @var User $user */
+ $user = User::query()->first();
$adminRole = Role::getSystemRole('admin');
$publicRole = Role::getSystemRole('public');
- $this->asAdmin()->visit('/settings/users/' . $user->id)
- ->seeElement('[name="roles[' . $adminRole->id . ']"]')
- ->seeElement('[name="roles[' . $publicRole->id . ']"]');
+ $this->asAdmin()->get('/settings/users/' . $user->id)
+ ->assertElementExists('[name="roles[' . $adminRole->id . ']"]')
+ ->assertElementExists('[name="roles[' . $publicRole->id . ']"]');
}
public function test_public_role_visible_in_role_listing()
{
- $this->asAdmin()->visit('/settings/roles')
- ->see('Admin')
- ->see('Public');
+ $this->asAdmin()->get('/settings/roles')
+ ->assertSee('Admin')
+ ->assertSee('Public');
}
public function test_public_role_visible_in_default_role_setting()
{
- $this->asAdmin()->visit('/settings')
- ->seeElement('[data-system-role-name="admin"]')
- ->seeElement('[data-system-role-name="public"]');
+ $this->asAdmin()->get('/settings')
+ ->assertElementExists('[data-system-role-name="admin"]')
+ ->assertElementExists('[data-system-role-name="public"]');
}
- public function test_public_role_not_deleteable()
+ public function test_public_role_not_deletable()
{
- $this->asAdmin()->visit('/settings/roles')
- ->click('Public')
- ->see('Edit Role')
- ->click('Delete Role')
- ->press('Confirm')
- ->see('Delete Role')
- ->see('Cannot be deleted');
+ /** @var Role $publicRole */
+ $publicRole = Role::getSystemRole('public');
+ $resp = $this->asAdmin()->delete('/settings/roles/delete/' . $publicRole->id);
+ $resp->assertRedirect('/');
+
+ $this->get('/settings/roles/delete/' . $publicRole->id);
+ $resp = $this->delete('/settings/roles/delete/' . $publicRole->id);
+ $resp->assertRedirect('/settings/roles/delete/' . $publicRole->id);
+ $resp = $this->get('/settings/roles/delete/' . $publicRole->id);
+ $resp->assertSee('This role is a system role and cannot be deleted');
}
public function test_image_delete_own_permission()
{
$this->giveUserPermissions($this->user, ['image-update-all']);
- $page = Page::first();
- $image = factory(Image::class)->create(['uploaded_to' => $page->id, 'created_by' => $this->user->id, 'updated_by' => $this->user->id]);
+ /** @var Page $page */
+ $page = Page::query()->first();
+ $image = factory(Image::class)->create([
+ 'uploaded_to' => $page->id,
+ 'created_by' => $this->user->id,
+ 'updated_by' => $this->user->id,
+ ]);
- $this->actingAs($this->user)->json('delete', '/images/' . $image->id)
- ->seeStatusCode(403);
+ $this->actingAs($this->user)->json('delete', '/images/' . $image->id)->assertStatus(403);
$this->giveUserPermissions($this->user, ['image-delete-own']);
- $this->actingAs($this->user)->json('delete', '/images/' . $image->id)
- ->seeStatusCode(200)
- ->dontSeeInDatabase('images', ['id' => $image->id]);
+ $this->actingAs($this->user)->json('delete', '/images/' . $image->id)->assertOk();
+ $this->assertDatabaseMissing('images', ['id' => $image->id]);
}
public function test_image_delete_all_permission()
{
$this->giveUserPermissions($this->user, ['image-update-all']);
$admin = $this->getAdmin();
- $page = Page::first();
+ /** @var Page $page */
+ $page = Page::query()->first();
$image = factory(Image::class)->create(['uploaded_to' => $page->id, 'created_by' => $admin->id, 'updated_by' => $admin->id]);
- $this->actingAs($this->user)->json('delete', '/images/' . $image->id)
- ->seeStatusCode(403);
+ $this->actingAs($this->user)->json('delete', '/images/' . $image->id)->assertStatus(403);
$this->giveUserPermissions($this->user, ['image-delete-own']);
- $this->actingAs($this->user)->json('delete', '/images/' . $image->id)
- ->seeStatusCode(403);
+ $this->actingAs($this->user)->json('delete', '/images/' . $image->id)->assertStatus(403);
$this->giveUserPermissions($this->user, ['image-delete-all']);
- $this->actingAs($this->user)->json('delete', '/images/' . $image->id)
- ->seeStatusCode(200)
- ->dontSeeInDatabase('images', ['id' => $image->id]);
+ $this->actingAs($this->user)->json('delete', '/images/' . $image->id)->assertOk();
+ $this->assertDatabaseMissing('images', ['id' => $image->id]);
}
public function test_role_permission_removal()
{
// To cover issue fixed in f99c8ff99aee9beb8c692f36d4b84dc6e651e50a.
- $page = Page::first();
+ /** @var Page $page */
+ $page = Page::query()->first();
$viewerRole = Role::getRole('viewer');
$viewer = $this->getViewer();
- $this->actingAs($viewer)->visit($page->getUrl())->assertResponseStatus(200);
+ $this->actingAs($viewer)->get($page->getUrl())->assertOk();
$this->asAdmin()->put('/settings/roles/' . $viewerRole->id, [
'display_name' => $viewerRole->display_name,
'description' => $viewerRole->description,
'permission' => [],
- ])->assertResponseStatus(302);
+ ])->assertStatus(302);
- $this->expectException(HttpException::class);
- $this->actingAs($viewer)->visit($page->getUrl())->assertResponseStatus(404);
+ $this->actingAs($viewer)->get($page->getUrl())->assertStatus(404);
}
public function test_empty_state_actions_not_visible_without_permission()
$admin = $this->getAdmin();
// Book links
$book = factory(Book::class)->create(['created_by' => $admin->id, 'updated_by' => $admin->id]);
- $this->updateEntityPermissions($book);
- $this->actingAs($this->getViewer())->visit($book->getUrl())
- ->dontSee('Create a new page')
- ->dontSee('Add a chapter');
+ $this->regenEntityPermissions($book);
+ $this->actingAs($this->getViewer())->get($book->getUrl())
+ ->assertDontSee('Create a new page')
+ ->assertDontSee('Add a chapter');
// Chapter links
$chapter = factory(Chapter::class)->create(['created_by' => $admin->id, 'updated_by' => $admin->id, 'book_id' => $book->id]);
- $this->updateEntityPermissions($chapter);
- $this->actingAs($this->getViewer())->visit($chapter->getUrl())
- ->dontSee('Create a new page')
- ->dontSee('Sort the current book');
+ $this->regenEntityPermissions($chapter);
+ $this->actingAs($this->getViewer())->get($chapter->getUrl())
+ ->assertDontSee('Create a new page')
+ ->assertDontSee('Sort the current book');
}
public function test_comment_create_permission()
{
$ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
- $this->actingAs($this->user)->addComment($ownPage);
-
- $this->assertResponseStatus(403);
+ $this->actingAs($this->user)
+ ->addComment($ownPage)
+ ->assertStatus(403);
$this->giveUserPermissions($this->user, ['comment-create-all']);
- $this->actingAs($this->user)->addComment($ownPage);
- $this->assertResponseStatus(200);
+ $this->actingAs($this->user)
+ ->addComment($ownPage)
+ ->assertOk();
}
public function test_comment_update_own_permission()
{
$ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
$this->giveUserPermissions($this->user, ['comment-create-all']);
- $commentId = $this->actingAs($this->user)->addComment($ownPage);
+ $this->actingAs($this->user)->addComment($ownPage);
+ /** @var Comment $comment */
+ $comment = $ownPage->comments()->latest()->first();
// no comment-update-own
- $this->actingAs($this->user)->updateComment($commentId);
- $this->assertResponseStatus(403);
+ $this->actingAs($this->user)->updateComment($comment)->assertStatus(403);
$this->giveUserPermissions($this->user, ['comment-update-own']);
// now has comment-update-own
- $this->actingAs($this->user)->updateComment($commentId);
- $this->assertResponseStatus(200);
+ $this->actingAs($this->user)->updateComment($comment)->assertOk();
}
public function test_comment_update_all_permission()
{
+ /** @var Page $ownPage */
$ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
- $commentId = $this->asAdmin()->addComment($ownPage);
+ $this->asAdmin()->addComment($ownPage);
+ /** @var Comment $comment */
+ $comment = $ownPage->comments()->latest()->first();
// no comment-update-all
- $this->actingAs($this->user)->updateComment($commentId);
- $this->assertResponseStatus(403);
+ $this->actingAs($this->user)->updateComment($comment)->assertStatus(403);
$this->giveUserPermissions($this->user, ['comment-update-all']);
// now has comment-update-all
- $this->actingAs($this->user)->updateComment($commentId);
- $this->assertResponseStatus(200);
+ $this->actingAs($this->user)->updateComment($comment)->assertOk();
}
public function test_comment_delete_own_permission()
{
+ /** @var Page $ownPage */
$ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
$this->giveUserPermissions($this->user, ['comment-create-all']);
- $commentId = $this->actingAs($this->user)->addComment($ownPage);
+ $this->actingAs($this->user)->addComment($ownPage);
+
+ /** @var Comment $comment */
+ $comment = $ownPage->comments()->latest()->first();
// no comment-delete-own
- $this->actingAs($this->user)->deleteComment($commentId);
- $this->assertResponseStatus(403);
+ $this->actingAs($this->user)->deleteComment($comment)->assertStatus(403);
$this->giveUserPermissions($this->user, ['comment-delete-own']);
// now has comment-update-own
- $this->actingAs($this->user)->deleteComment($commentId);
- $this->assertResponseStatus(200);
+ $this->actingAs($this->user)->deleteComment($comment)->assertOk();
}
public function test_comment_delete_all_permission()
{
+ /** @var Page $ownPage */
$ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
- $commentId = $this->asAdmin()->addComment($ownPage);
+ $this->asAdmin()->addComment($ownPage);
+ /** @var Comment $comment */
+ $comment = $ownPage->comments()->latest()->first();
// no comment-delete-all
- $this->actingAs($this->user)->deleteComment($commentId);
- $this->assertResponseStatus(403);
+ $this->actingAs($this->user)->deleteComment($comment)->assertStatus(403);
$this->giveUserPermissions($this->user, ['comment-delete-all']);
// now has comment-delete-all
- $this->actingAs($this->user)->deleteComment($commentId);
- $this->assertResponseStatus(200);
+ $this->actingAs($this->user)->deleteComment($comment)->assertOk();
}
- private function addComment($page)
+ private function addComment(Page $page): TestResponse
{
$comment = factory(Comment::class)->make();
- $url = "/comment/$page->id";
- $request = [
- 'text' => $comment->text,
- 'html' => $comment->html,
- ];
-
- $this->postJson($url, $request);
- $comment = $page->comments()->first();
- return $comment === null ? null : $comment->id;
+ return $this->postJson("/comment/$page->id", $comment->only('text', 'html'));
}
- private function updateComment($commentId)
+ private function updateComment(Comment $comment): TestResponse
{
- $comment = factory(Comment::class)->make();
- $url = "/comment/$commentId";
- $request = [
- 'text' => $comment->text,
- 'html' => $comment->html,
- ];
+ $commentData = factory(Comment::class)->make();
- return $this->putJson($url, $request);
+ return $this->putJson("/comment/{$comment->id}", $commentData->only('text', 'html'));
}
- private function deleteComment($commentId)
+ private function deleteComment(Comment $comment): TestResponse
{
- $url = '/comment/' . $commentId;
-
- return $this->json('DELETE', $url);
+ return $this->json('DELETE', '/comment/' . $comment->id);
}
}
namespace Tests;
-use Auth;
use BookStack\Auth\Permissions\PermissionService;
use BookStack\Auth\Permissions\RolePermission;
use BookStack\Auth\Role;
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;
class PublicActionTest extends TestCase
use BookStack\Entities\Models\Deletion;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\Page;
-use DB;
use Illuminate\Support\Carbon;
+use Illuminate\Support\Facades\DB;
class RecycleBinTest extends TestCase
{
namespace Tests;
-use Illuminate\Support\Str;
+use BookStack\Util\CspService;
class SecurityHeaderTest extends TestCase
{
public function test_iframe_csp_self_only_by_default()
{
$resp = $this->get('/');
- $cspHeaders = collect($resp->headers->get('Content-Security-Policy'));
- $frameHeaders = $cspHeaders->filter(function ($val) {
- return Str::startsWith($val, 'frame-ancestors');
- });
+ $frameHeader = $this->getCspHeader($resp, 'frame-ancestors');
- $this->assertTrue($frameHeaders->count() === 1);
- $this->assertEquals('frame-ancestors \'self\'', $frameHeaders->first());
+ $this->assertEquals('frame-ancestors \'self\'', $frameHeader);
}
public function test_iframe_csp_includes_extra_hosts_if_configured()
{
$this->runWithEnv('ALLOWED_IFRAME_HOSTS', 'https://a.example.com https://b.example.com', function () {
$resp = $this->get('/');
- $cspHeaders = collect($resp->headers->get('Content-Security-Policy'));
- $frameHeaders = $cspHeaders->filter(function ($val) {
- return Str::startsWith($val, 'frame-ancestors');
- });
+ $frameHeader = $this->getCspHeader($resp, 'frame-ancestors');
- $this->assertTrue($frameHeaders->count() === 1);
- $this->assertEquals('frame-ancestors \'self\' https://a.example.com https://b.example.com', $frameHeaders->first());
+ $this->assertNotEmpty($frameHeader);
+ $this->assertEquals('frame-ancestors \'self\' https://a.example.com https://b.example.com', $frameHeader);
});
}
+
+ public function test_script_csp_set_on_responses()
+ {
+ $resp = $this->get('/');
+ $scriptHeader = $this->getCspHeader($resp, 'script-src');
+ $this->assertStringContainsString('\'strict-dynamic\'', $scriptHeader);
+ $this->assertStringContainsString('\'nonce-', $scriptHeader);
+ }
+
+ public function test_script_csp_nonce_matches_nonce_used_in_custom_head()
+ {
+ $this->setSettings(['app-custom-head' => '<script>console.log("cat");</script>']);
+ $resp = $this->get('/login');
+ $scriptHeader = $this->getCspHeader($resp, 'script-src');
+
+ $nonce = app()->make(CspService::class)->getNonce();
+ $this->assertStringContainsString('nonce-' . $nonce, $scriptHeader);
+ $resp->assertSee('<script nonce="' . $nonce . '">console.log("cat");</script>');
+ }
+
+ public function test_script_csp_nonce_changes_per_request()
+ {
+ $resp = $this->get('/');
+ $firstHeader = $this->getCspHeader($resp, 'script-src');
+
+ $this->refreshApplication();
+
+ $resp = $this->get('/');
+ $secondHeader = $this->getCspHeader($resp, 'script-src');
+
+ $this->assertNotEquals($firstHeader, $secondHeader);
+ }
+
+ public function test_allow_content_scripts_settings_controls_csp_script_headers()
+ {
+ config()->set('app.allow_content_scripts', true);
+ $resp = $this->get('/');
+ $scriptHeader = $this->getCspHeader($resp, 'script-src');
+ $this->assertEmpty($scriptHeader);
+
+ config()->set('app.allow_content_scripts', false);
+ $resp = $this->get('/');
+ $scriptHeader = $this->getCspHeader($resp, 'script-src');
+ $this->assertNotEmpty($scriptHeader);
+ }
+
+ public function test_object_src_csp_header_set()
+ {
+ $resp = $this->get('/');
+ $scriptHeader = $this->getCspHeader($resp, 'object-src');
+ $this->assertEquals('object-src \'self\'', $scriptHeader);
+ }
+
+ public function test_base_uri_csp_header_set()
+ {
+ $resp = $this->get('/');
+ $scriptHeader = $this->getCspHeader($resp, 'base-uri');
+ $this->assertEquals('base-uri \'self\'', $scriptHeader);
+ }
+
+ /**
+ * Get the value of the first CSP header of the given type.
+ */
+ protected function getCspHeader(TestResponse $resp, string $type): string
+ {
+ $cspHeaders = collect($resp->headers->all('Content-Security-Policy'));
+
+ return $cspHeaders->filter(function ($val) use ($type) {
+ return strpos($val, $type) === 0;
+ })->first() ?? '';
+ }
}
--- /dev/null
+<?php
+
+namespace Tests\Settings;
+
+use BookStack\Util\CspService;
+use Tests\TestCase;
+
+class CustomHeadContentTest extends TestCase
+{
+ public function test_configured_content_shows_on_pages()
+ {
+ $this->setSettings(['app-custom-head' => '<script>console.log("cat");</script>']);
+ $resp = $this->get('/login');
+ $resp->assertSee('console.log("cat")');
+ }
+
+ public function test_configured_content_does_not_show_on_settings_page()
+ {
+ $this->setSettings(['app-custom-head' => '<script>console.log("cat");</script>']);
+ $resp = $this->asAdmin()->get('/settings');
+ $resp->assertDontSee('console.log("cat")');
+ }
+
+ public function test_divs_in_js_preserved_in_configured_content()
+ {
+ $this->setSettings(['app-custom-head' => '<script><div id="hello">cat</div></script>']);
+ $resp = $this->get('/login');
+ $resp->assertSee('<div id="hello">cat</div>');
+ }
+
+ public function test_nonce_application_handles_edge_cases()
+ {
+ $mockCSP = $this->mock(CspService::class);
+ $mockCSP->shouldReceive('getNonce')->andReturn('abc123');
+
+ $content = trim('
+<script>console.log("cat");</script>
+<script type="text/html"><\script>const a = `<div></div>`<\/\script></script>
+<script >const a = `<div></div>`;</script>
+<script type="<script text>test">const c = `<div></div>`;</script>
+<script
+ type="text/html"
+>
+const a = `<\script><\/script>`;
+const b = `<script`;
+</script>
+<SCRIPT>const b = `↗️£`;</SCRIPT>
+ ');
+
+ $expectedOutput = trim('
+<script nonce="abc123">console.log("cat");</script>
+<script type="text/html" nonce="abc123"><\script>const a = `<div></div>`<\/\script></script>
+<script nonce="abc123">const a = `<div></div>`;</script>
+<script type="<script text>test" nonce="abc123">const c = `<div></div>`;</script>
+<script type="text/html" nonce="abc123">
+const a = `<\script><\/script>`;
+const b = `<script`;
+</script>
+<script nonce="abc123">const b = `↗️£`;</script>
+ ');
+
+ $this->setSettings(['app-custom-head' => $content]);
+ $resp = $this->get('/login');
+ $resp->assertSee($expectedOutput);
+ }
+}
<?php
+namespace Tests\Settings;
+
use Tests\TestCase;
class FooterLinksTest extends TestCase
use BookStack\Auth\Permissions\PermissionService;
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\Settings\SettingService;
use BookStack\Uploads\HttpFetcher;
use Illuminate\Foundation\Testing\Assert as PHPUnit;
+use Illuminate\Http\JsonResponse;
use Illuminate\Support\Env;
use Illuminate\Support\Facades\Log;
use Mockery;
/**
* Get a user that's not a system user such as the guest user.
*/
- public function getNormalUser()
+ public function getNormalUser(): User
{
return User::query()->where('system_name', '=', null)->get()->last();
}
$user->clearPermissionCache();
}
+ /**
+ * Completely remove the given permission name from the given user.
+ */
+ protected function removePermissionFromUser(User $user, string $permission)
+ {
+ $permission = RolePermission::query()->where('name', '=', $permission)->first();
+ /** @var Role $role */
+ foreach ($user->roles as $role) {
+ $role->detachPermission($permission);
+ }
+ $user->clearPermissionCache();
+ }
+
/**
* Create a new basic role for testing purposes.
*/
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 = factory(Book::class)->create($userAttrs);
+ $chapter = factory(Chapter::class)->create(array_merge(['book_id' => $book->id], $userAttrs));
+ $page = factory(Page::class)->create(array_merge(['book_id' => $book->id, 'chapter_id' => $chapter->id], $userAttrs));
+ $restrictionService = $this->app[PermissionService::class];
+ $restrictionService->buildJointPermissionsForEntity($book);
+
+ return compact('book', 'chapter', 'page');
+ }
+
/**
* Mock the HttpFetcher service and return the given data on fetch.
*/
private function isPermissionError($response): bool
{
return $response->status() === 302
- && $response->headers->get('Location') === url('/')
- && strpos(session()->pull('error', ''), 'You do not have permission to access') === 0;
+ && (
+ (
+ $response->headers->get('Location') === url('/')
+ && strpos(session()->pull('error', ''), 'You do not have permission to access') === 0
+ )
+ ||
+ (
+ $response instanceof JsonResponse &&
+ $response->json(['error' => 'You do not have permission to perform the requested action.'])
+ )
+ );
}
/**
* Assert that an activity entry exists of the given key.
* Checks the activity belongs to the given entity if provided.
*/
- protected function assertActivityExists(string $type, Entity $entity = null)
+ protected function assertActivityExists(string $type, ?Entity $entity = null, string $detail = '')
{
$detailsToCheck = ['type' => $type];
$detailsToCheck['entity_id'] = $entity->id;
}
+ if ($detail) {
+ $detailsToCheck['detail'] = $detail;
+ }
+
$this->assertDatabaseHas('activities', $detailsToCheck);
}
}
return $this->crawlerInstance;
}
+ /**
+ * Get the HTML of the first element at the given selector.
+ */
+ public function getElementHtml(string $selector): string
+ {
+ return $this->crawler()->filter($selector)->first()->outerHtml();
+ }
+
/**
* Assert the response contains the specified element.
*
use BookStack\Entities\Tools\PageContent;
use BookStack\Facades\Theme;
use BookStack\Theming\ThemeEvents;
-use File;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
+use Illuminate\Support\Facades\File;
use League\CommonMark\ConfigurableEnvironmentInterface;
class ThemeTest extends TestCase
);
}
+ public function test_dompdf_remote_fetching_controlled_by_allow_untrusted_server_fetching_false()
+ {
+ $this->checkEnvConfigResult('ALLOW_UNTRUSTED_SERVER_FETCHING', 'false', 'dompdf.defines.enable_remote', false);
+ $this->checkEnvConfigResult('ALLOW_UNTRUSTED_SERVER_FETCHING', 'true', 'dompdf.defines.enable_remote', true);
+ }
+
/**
* Set an environment variable of the given name and value
* then check the given config key to see if it matches the given result.
namespace Tests\User;
use BookStack\Actions\ActivityType;
+use BookStack\Auth\Role;
use BookStack\Auth\User;
use BookStack\Entities\Models\Page;
+use Illuminate\Support\Facades\Hash;
+use Illuminate\Support\Str;
use Tests\TestCase;
class UserManagementTest extends TestCase
{
+ public function test_user_creation()
+ {
+ /** @var User $user */
+ $user = factory(User::class)->make();
+ $adminRole = Role::getRole('admin');
+
+ $resp = $this->asAdmin()->get('/settings/users');
+ $resp->assertElementContains('a[href="' . url('/settings/users/create') . '"]', 'Add New User');
+
+ $this->get('/settings/users/create')
+ ->assertElementContains('form[action="' . url('/settings/users/create') . '"]', 'Save');
+
+ $resp = $this->post('/settings/users/create', [
+ 'name' => $user->name,
+ 'email' => $user->email,
+ 'password' => $user->password,
+ 'password-confirm' => $user->password,
+ 'roles[' . $adminRole->id . ']' => 'true',
+ ]);
+ $resp->assertRedirect('/settings/users');
+
+ $resp = $this->get('/settings/users');
+ $resp->assertSee($user->name);
+
+ $this->assertDatabaseHas('users', $user->only('name', 'email'));
+
+ $user->refresh();
+ $this->assertStringStartsWith(Str::slug($user->name), $user->slug);
+ }
+
+ public function test_user_updating()
+ {
+ $user = $this->getNormalUser();
+ $password = $user->password;
+
+ $resp = $this->asAdmin()->get('/settings/users/' . $user->id);
+ $resp->assertSee($user->email);
+
+ $this->put($user->getEditUrl(), [
+ 'name' => 'Barry Scott',
+ ])->assertRedirect('/settings/users');
+
+ $this->assertDatabaseHas('users', ['id' => $user->id, 'name' => 'Barry Scott', 'password' => $password]);
+ $this->assertDatabaseMissing('users', ['name' => $user->name]);
+
+ $user->refresh();
+ $this->assertStringStartsWith(Str::slug($user->name), $user->slug);
+ }
+
+ public function test_user_password_update()
+ {
+ $user = $this->getNormalUser();
+ $userProfilePage = '/settings/users/' . $user->id;
+
+ $this->asAdmin()->get($userProfilePage);
+ $this->put($userProfilePage, [
+ 'password' => 'newpassword',
+ ])->assertRedirect($userProfilePage);
+
+ $this->get($userProfilePage)->assertSee('Password confirmation required');
+
+ $this->put($userProfilePage, [
+ 'password' => 'newpassword',
+ 'password-confirm' => 'newpassword',
+ ])->assertRedirect('/settings/users');
+
+ $userPassword = User::query()->find($user->id)->password;
+ $this->assertTrue(Hash::check('newpassword', $userPassword));
+ }
+
+ public function test_user_cannot_be_deleted_if_last_admin()
+ {
+ $adminRole = Role::getRole('admin');
+
+ // Delete all but one admin user if there are more than one
+ $adminUsers = $adminRole->users;
+ if (count($adminUsers) > 1) {
+ /** @var User $user */
+ foreach ($adminUsers->splice(1) as $user) {
+ $user->delete();
+ }
+ }
+
+ // Ensure we currently only have 1 admin user
+ $this->assertEquals(1, $adminRole->users()->count());
+ /** @var User $user */
+ $user = $adminRole->users->first();
+
+ $resp = $this->asAdmin()->delete('/settings/users/' . $user->id);
+ $resp->assertRedirect('/settings/users/' . $user->id);
+
+ $resp = $this->get('/settings/users/' . $user->id);
+ $resp->assertSee('You cannot delete the only admin');
+
+ $this->assertDatabaseHas('users', ['id' => $user->id]);
+ }
+
public function test_delete()
{
$editor = $this->getEditor();
'owned_by' => $newOwner->id,
]);
}
+
+ public function test_guest_profile_shows_limited_form()
+ {
+ $guest = User::getDefault();
+ $resp = $this->asAdmin()->get('/settings/users/' . $guest->id);
+ $resp->assertSee('Guest');
+ $resp->assertElementNotExists('#password');
+ }
+
+ public function test_guest_profile_cannot_be_deleted()
+ {
+ $guestUser = User::getDefault();
+ $resp = $this->asAdmin()->get('/settings/users/' . $guestUser->id . '/delete');
+ $resp->assertSee('Delete User');
+ $resp->assertSee('Guest');
+ $resp->assertElementContains('form[action$="/settings/users/' . $guestUser->id . '"] button', 'Confirm');
+
+ $resp = $this->delete('/settings/users/' . $guestUser->id);
+ $resp->assertRedirect('/settings/users/' . $guestUser->id);
+ $resp = $this->followRedirects($resp);
+ $resp->assertSee('cannot delete the guest user');
+ }
}
namespace Tests\User;
+use BookStack\Entities\Models\Bookshelf;
use Tests\TestCase;
class UserPreferencesTest extends TestCase
$home = $this->get('/login');
$home->assertElementExists('.dark-mode');
}
+
+ public function test_books_view_type_preferences_when_list()
+ {
+ $editor = $this->getEditor();
+ setting()->putUser($editor, 'books_view_type', 'list');
+
+ $this->actingAs($editor)->get('/books')
+ ->assertElementNotExists('.featured-image-container')
+ ->assertElementExists('.content-wrap .entity-list-item');
+ }
+
+ public function test_books_view_type_preferences_when_grid()
+ {
+ $editor = $this->getEditor();
+ setting()->putUser($editor, 'books_view_type', 'grid');
+
+ $this->actingAs($editor)->get('/books')
+ ->assertElementExists('.featured-image-container');
+ }
+
+ public function test_shelf_view_type_change()
+ {
+ $editor = $this->getEditor();
+ /** @var Bookshelf $shelf */
+ $shelf = Bookshelf::query()->first();
+ setting()->putUser($editor, 'bookshelf_view_type', 'list');
+
+ $this->actingAs($editor)->get($shelf->getUrl())
+ ->assertElementNotExists('.featured-image-container')
+ ->assertElementExists('.content-wrap .entity-list-item')
+ ->assertSee('Grid View');
+
+ $req = $this->patch("/settings/users/{$editor->id}/switch-shelf-view", ['view_type' => 'grid']);
+ $req->assertRedirect($shelf->getUrl());
+
+ $this->actingAs($editor)->get($shelf->getUrl())
+ ->assertElementExists('.featured-image-container')
+ ->assertElementNotExists('.content-wrap .entity-list-item')
+ ->assertSee('List View');
+ }
}
use Activity;
use BookStack\Actions\ActivityType;
use BookStack\Auth\User;
-use BookStack\Entities\Models\Bookshelf;
-use Tests\BrowserKitTest;
+use Tests\TestCase;
-class UserProfileTest extends BrowserKitTest
+class UserProfileTest extends TestCase
{
+ /**
+ * @var User
+ */
protected $user;
public function setUp(): void
public function test_profile_page_shows_name()
{
$this->asAdmin()
- ->visit('/user/' . $this->user->slug)
- ->see($this->user->name);
+ ->get('/user/' . $this->user->slug)
+ ->assertSee($this->user->name);
}
public function test_profile_page_shows_recent_entities()
{
$content = $this->createEntityChainBelongingToUser($this->user, $this->user);
- $this->asAdmin()
- ->visit('/user/' . $this->user->slug)
- // Check the recently created page is shown
- ->see($content['page']->name)
- // Check the recently created chapter is shown
- ->see($content['chapter']->name)
- // Check the recently created book is shown
- ->see($content['book']->name);
+ $resp = $this->asAdmin()->get('/user/' . $this->user->slug);
+ // Check the recently created page is shown
+ $resp->assertSee($content['page']->name);
+ // Check the recently created chapter is shown
+ $resp->assertSee($content['chapter']->name);
+ // Check the recently created book is shown
+ $resp->assertSee($content['book']->name);
}
public function test_profile_page_shows_created_content_counts()
{
- $newUser = $this->getNewBlankUser();
+ $newUser = factory(User::class)->create();
- $this->asAdmin()->visit('/user/' . $newUser->slug)
- ->see($newUser->name)
- ->seeInElement('#content-counts', '0 Books')
- ->seeInElement('#content-counts', '0 Chapters')
- ->seeInElement('#content-counts', '0 Pages');
+ $this->asAdmin()->get('/user/' . $newUser->slug)
+ ->assertSee($newUser->name)
+ ->assertElementContains('#content-counts', '0 Books')
+ ->assertElementContains('#content-counts', '0 Chapters')
+ ->assertElementContains('#content-counts', '0 Pages');
$this->createEntityChainBelongingToUser($newUser, $newUser);
- $this->asAdmin()->visit('/user/' . $newUser->slug)
- ->see($newUser->name)
- ->seeInElement('#content-counts', '1 Book')
- ->seeInElement('#content-counts', '1 Chapter')
- ->seeInElement('#content-counts', '1 Page');
+ $this->asAdmin()->get('/user/' . $newUser->slug)
+ ->assertSee($newUser->name)
+ ->assertElementContains('#content-counts', '1 Book')
+ ->assertElementContains('#content-counts', '1 Chapter')
+ ->assertElementContains('#content-counts', '1 Page');
}
public function test_profile_page_shows_recent_activity()
{
- $newUser = $this->getNewBlankUser();
+ $newUser = factory(User::class)->create();
$this->actingAs($newUser);
$entities = $this->createEntityChainBelongingToUser($newUser, $newUser);
Activity::addForEntity($entities['book'], ActivityType::BOOK_UPDATE);
Activity::addForEntity($entities['page'], ActivityType::PAGE_CREATE);
- $this->asAdmin()->visit('/user/' . $newUser->slug)
- ->seeInElement('#recent-user-activity', 'updated book')
- ->seeInElement('#recent-user-activity', 'created page')
- ->seeInElement('#recent-user-activity', $entities['page']->name);
+ $this->asAdmin()->get('/user/' . $newUser->slug)
+ ->assertElementContains('#recent-user-activity', 'updated book')
+ ->assertElementContains('#recent-user-activity', 'created page')
+ ->assertElementContains('#recent-user-activity', $entities['page']->name);
}
- public function test_clicking_user_name_in_activity_leads_to_profile_page()
+ public function test_user_activity_has_link_leading_to_profile()
{
- $newUser = $this->getNewBlankUser();
+ $newUser = factory(User::class)->create();
$this->actingAs($newUser);
$entities = $this->createEntityChainBelongingToUser($newUser, $newUser);
Activity::addForEntity($entities['book'], ActivityType::BOOK_UPDATE);
Activity::addForEntity($entities['page'], ActivityType::PAGE_CREATE);
- $this->asAdmin()->visit('/')->clickInElement('#recent-activity', $newUser->name)
- ->seePageIs('/user/' . $newUser->slug)
- ->see($newUser->name);
+ $linkSelector = '#recent-activity a[href$="/user/' . $newUser->slug . '"]';
+ $this->asAdmin()->get('/')
+ ->assertElementContains($linkSelector, $newUser->name);
}
public function test_profile_has_search_links_in_created_entity_lists()
{
$user = $this->getEditor();
- $resp = $this->actingAs($this->getAdmin())->visit('/user/' . $user->slug);
+ $resp = $this->actingAs($this->getAdmin())->get('/user/' . $user->slug);
$expectedLinks = [
'/search?term=%7Bcreated_by%3A' . $user->slug . '%7D+%7Btype%3Apage%7D',
];
foreach ($expectedLinks as $link) {
- $resp->seeInElement('[href$="' . $link . '"]', 'View All');
+ $resp->assertElementContains('[href$="' . $link . '"]', 'View All');
}
}
-
- public function test_guest_profile_shows_limited_form()
- {
- $this->asAdmin()
- ->visit('/settings/users')
- ->click('Guest')
- ->dontSeeElement('#password');
- }
-
- public function test_guest_profile_cannot_be_deleted()
- {
- $guestUser = User::getDefault();
- $this->asAdmin()->visit('/settings/users/' . $guestUser->id . '/delete')
- ->see('Delete User')->see('Guest')
- ->press('Confirm')
- ->seePageIs('/settings/users/' . $guestUser->id)
- ->see('cannot delete the guest user');
- }
-
- public function test_books_view_is_list()
- {
- $editor = $this->getEditor();
- setting()->putUser($editor, 'books_view_type', 'list');
-
- $this->actingAs($editor)
- ->visit('/books')
- ->pageNotHasElement('.featured-image-container')
- ->pageHasElement('.content-wrap .entity-list-item');
- }
-
- public function test_books_view_is_grid()
- {
- $editor = $this->getEditor();
- setting()->putUser($editor, 'books_view_type', 'grid');
-
- $this->actingAs($editor)
- ->visit('/books')
- ->pageHasElement('.featured-image-container');
- }
-
- public function test_shelf_view_type_change()
- {
- $editor = $this->getEditor();
- $shelf = Bookshelf::query()->first();
- setting()->putUser($editor, 'bookshelf_view_type', 'list');
-
- $this->actingAs($editor)->visit($shelf->getUrl())
- ->pageNotHasElement('.featured-image-container')
- ->pageHasElement('.content-wrap .entity-list-item')
- ->see('Grid View');
-
- $req = $this->patch("/settings/users/{$editor->id}/switch-shelf-view", ['view_type' => 'grid']);
- $req->assertRedirectedTo($shelf->getUrl());
-
- $this->actingAs($editor)->visit($shelf->getUrl())
- ->pageHasElement('.featured-image-container')
- ->pageNotHasElement('.content-wrap .entity-list-item')
- ->see('List View');
- }
}