mirror of
https://github.com/BookStackApp/BookStack.git
synced 2026-02-10 03:12:20 +03:00
Compare commits
24 Commits
v22.11.1
...
user_permi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3083979855 | ||
|
|
55642a33ee | ||
|
|
93ba572369 | ||
|
|
a825f27930 | ||
|
|
932e1d7c61 | ||
|
|
2f1491c5a4 | ||
|
|
026e9030b9 | ||
|
|
451e4ac452 | ||
|
|
7330139555 | ||
|
|
39acbeac68 | ||
|
|
2d9d2bba80 | ||
|
|
adabf06dbe | ||
|
|
5ffc10e688 | ||
|
|
6a6f5e4d19 | ||
|
|
491beee93e | ||
|
|
f844ae0902 | ||
|
|
d54ea1b3ed | ||
|
|
e8a8fedfd6 | ||
|
|
60bf838a4a | ||
|
|
0411185fbb | ||
|
|
93cbd3b8aa | ||
|
|
7a269e7689 | ||
|
|
f8c4725166 | ||
|
|
1c53ffc4d1 |
8
.github/translators.txt
vendored
8
.github/translators.txt
vendored
@@ -283,13 +283,13 @@ Kuchinashi Hoshikawa (kuchinashi) :: Chinese Simplified
|
||||
digilady :: Greek
|
||||
Linus (LinusOP) :: Swedish
|
||||
Felipe Cardoso (felipecardosoruff) :: Portuguese, Brazilian
|
||||
RandomUser0815 :: German Informal; German
|
||||
RandomUser0815 :: German
|
||||
Ismael Mesquita (mesquitoliveira) :: Portuguese, Brazilian
|
||||
구인회 (laskdjlaskdj12) :: Korean
|
||||
LiZerui (CNLiZerui) :: Chinese Traditional
|
||||
Fabrice Boyer (FabriceBoyer) :: French
|
||||
mikael (bitcanon) :: Swedish
|
||||
Matthias Mai (schnapsidee) :: German; German Informal
|
||||
Matthias Mai (schnapsidee) :: German
|
||||
Ufuk Ayyıldız (ufukayyildiz) :: Turkish
|
||||
Jan Mitrof (jan.kachlik) :: Czech
|
||||
edwardsmirnov :: Russian
|
||||
@@ -298,7 +298,3 @@ shotu :: French
|
||||
Cesar_Lopez_Aguillon :: Spanish
|
||||
bdewoop :: German
|
||||
dina davoudi (dina.davoudi) :: Persian
|
||||
Angelos Chouvardas (achouvardas) :: Greek
|
||||
rndrss :: Portuguese, Brazilian
|
||||
rirac294 :: Russian
|
||||
David Furman (thefourCraft) :: Hebrew
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -5,10 +5,10 @@ Homestead.yaml
|
||||
.idea
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
/public/dist/*.map
|
||||
/public/dist
|
||||
/public/plugins
|
||||
/public/css/*.map
|
||||
/public/js/*.map
|
||||
/public/css
|
||||
/public/js
|
||||
/public/bower
|
||||
/public/build/
|
||||
/storage/images
|
||||
|
||||
@@ -15,6 +15,8 @@ class ActivityQueries
|
||||
{
|
||||
protected PermissionApplicator $permissions;
|
||||
|
||||
protected array $fieldsForLists = ['id', 'type', 'detail', 'activities.entity_type', 'activities.entity_id', 'user_id', 'created_at'];
|
||||
|
||||
public function __construct(PermissionApplicator $permissions)
|
||||
{
|
||||
$this->permissions = $permissions;
|
||||
@@ -25,9 +27,11 @@ class ActivityQueries
|
||||
*/
|
||||
public function latest(int $count = 20, int $page = 0): array
|
||||
{
|
||||
$query = Activity::query()->select($this->fieldsForLists);
|
||||
$activityList = $this->permissions
|
||||
->restrictEntityRelationQuery(Activity::query(), 'activities', 'entity_id', 'entity_type')
|
||||
->restrictEntityRelationQuery($query, 'activities', 'entity_id', 'entity_type')
|
||||
->orderBy('created_at', 'desc')
|
||||
->whereNotNull('activities.entity_id')
|
||||
->with(['user', 'entity'])
|
||||
->skip($count * $page)
|
||||
->take($count)
|
||||
@@ -78,10 +82,12 @@ class ActivityQueries
|
||||
*/
|
||||
public function userActivity(User $user, int $count = 20, int $page = 0): array
|
||||
{
|
||||
$query = Activity::query()->select($this->fieldsForLists);
|
||||
$activityList = $this->permissions
|
||||
->restrictEntityRelationQuery(Activity::query(), 'activities', 'entity_id', 'entity_type')
|
||||
->restrictEntityRelationQuery($query, 'activities', 'entity_id', 'entity_type')
|
||||
->orderBy('created_at', 'desc')
|
||||
->where('user_id', '=', $user->id)
|
||||
->whereNotNull('activities.entity_id')
|
||||
->skip($count * $page)
|
||||
->take($count)
|
||||
->get();
|
||||
|
||||
@@ -29,15 +29,16 @@ class TagRepo
|
||||
$sort = 'value';
|
||||
}
|
||||
|
||||
$entityTypeCol = DB::getTablePrefix() . 'tags.entity_type';
|
||||
$query = Tag::query()
|
||||
->select([
|
||||
'name',
|
||||
($searchTerm || $nameFilter) ? 'value' : DB::raw('COUNT(distinct value) as `values`'),
|
||||
DB::raw('COUNT(id) as usages'),
|
||||
DB::raw('SUM(IF(entity_type = \'page\', 1, 0)) as page_count'),
|
||||
DB::raw('SUM(IF(entity_type = \'chapter\', 1, 0)) as chapter_count'),
|
||||
DB::raw('SUM(IF(entity_type = \'book\', 1, 0)) as book_count'),
|
||||
DB::raw('SUM(IF(entity_type = \'bookshelf\', 1, 0)) as shelf_count'),
|
||||
DB::raw("SUM(IF({$entityTypeCol} = 'page', 1, 0)) as page_count"),
|
||||
DB::raw("SUM(IF({$entityTypeCol} = 'chapter', 1, 0)) as chapter_count"),
|
||||
DB::raw("SUM(IF({$entityTypeCol} = 'book', 1, 0)) as book_count"),
|
||||
DB::raw("SUM(IF({$entityTypeCol} = 'bookshelf', 1, 0)) as shelf_count"),
|
||||
])
|
||||
->orderBy($sort, $listOptions->getOrder());
|
||||
|
||||
|
||||
18
app/Auth/Permissions/CollapsedPermission.php
Normal file
18
app/Auth/Permissions/CollapsedPermission.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Auth\Permissions;
|
||||
|
||||
use BookStack\Model;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property ?int $role_id
|
||||
* @property ?int $user_id
|
||||
* @property string $entity_type
|
||||
* @property int $entity_id
|
||||
* @property bool $view
|
||||
*/
|
||||
class CollapsedPermission extends Model
|
||||
{
|
||||
protected $table = 'entity_permissions_collapsed';
|
||||
}
|
||||
278
app/Auth/Permissions/CollapsedPermissionBuilder.php
Normal file
278
app/Auth/Permissions/CollapsedPermissionBuilder.php
Normal file
@@ -0,0 +1,278 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Auth\Permissions;
|
||||
|
||||
use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Models\BookChild;
|
||||
use BookStack\Entities\Models\Bookshelf;
|
||||
use BookStack\Entities\Models\Chapter;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Collapsed permissions act as a "flattened" view of entity-level permissions in the system
|
||||
* so inheritance does not have to managed as part of permission querying.
|
||||
*/
|
||||
class CollapsedPermissionBuilder
|
||||
{
|
||||
/**
|
||||
* Re-generate all collapsed permissions from scratch.
|
||||
*/
|
||||
public function rebuildForAll()
|
||||
{
|
||||
DB::table('entity_permissions_collapsed')->truncate();
|
||||
|
||||
// Chunk through all books
|
||||
$this->bookFetchQuery()->chunk(5, function (EloquentCollection $books) {
|
||||
$this->buildForBooks($books, false);
|
||||
});
|
||||
|
||||
// Chunk through all bookshelves
|
||||
Bookshelf::query()->withTrashed()
|
||||
->select(['id'])
|
||||
->chunk(50, function (EloquentCollection $shelves) {
|
||||
$this->generateCollapsedPermissions($shelves->all());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild the collapsed permissions for a particular entity.
|
||||
*/
|
||||
public function rebuildForEntity(Entity $entity)
|
||||
{
|
||||
$entities = [$entity];
|
||||
if ($entity instanceof Book) {
|
||||
$books = $this->bookFetchQuery()->where('id', '=', $entity->id)->get();
|
||||
$this->buildForBooks($books, true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var BookChild $entity */
|
||||
if ($entity->book) {
|
||||
$entities[] = $entity->book;
|
||||
}
|
||||
|
||||
if ($entity instanceof Page && $entity->chapter_id) {
|
||||
$entities[] = $entity->chapter;
|
||||
}
|
||||
|
||||
if ($entity instanceof Chapter) {
|
||||
foreach ($entity->pages as $page) {
|
||||
$entities[] = $page;
|
||||
}
|
||||
}
|
||||
|
||||
$this->buildForEntities($entities);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a query for fetching a book with its children.
|
||||
*/
|
||||
protected function bookFetchQuery(): Builder
|
||||
{
|
||||
return Book::query()->withTrashed()
|
||||
->select(['id'])->with([
|
||||
'chapters' => function ($query) {
|
||||
$query->withTrashed()->select(['id', 'book_id']);
|
||||
},
|
||||
'pages' => function ($query) {
|
||||
$query->withTrashed()->select(['id', 'book_id', 'chapter_id']);
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build collapsed permissions for the given books.
|
||||
*/
|
||||
protected function buildForBooks(EloquentCollection $books, bool $deleteOld)
|
||||
{
|
||||
$entities = clone $books;
|
||||
|
||||
/** @var Book $book */
|
||||
foreach ($books->all() as $book) {
|
||||
foreach ($book->getRelation('chapters') as $chapter) {
|
||||
$entities->push($chapter);
|
||||
}
|
||||
foreach ($book->getRelation('pages') as $page) {
|
||||
$entities->push($page);
|
||||
}
|
||||
}
|
||||
|
||||
if ($deleteOld) {
|
||||
$this->deleteForEntities($entities->all());
|
||||
}
|
||||
|
||||
$this->generateCollapsedPermissions($entities->all());
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild the collapsed permissions for a collection of entities.
|
||||
*/
|
||||
protected function buildForEntities(array $entities)
|
||||
{
|
||||
$this->deleteForEntities($entities);
|
||||
$this->generateCollapsedPermissions($entities);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the stored collapsed permissions for a list of entities.
|
||||
*
|
||||
* @param Entity[] $entities
|
||||
*/
|
||||
protected function deleteForEntities(array $entities)
|
||||
{
|
||||
$simpleEntities = $this->entitiesToSimpleEntities($entities);
|
||||
$idsByType = $this->entitiesToTypeIdMap($simpleEntities);
|
||||
|
||||
DB::transaction(function () use ($idsByType) {
|
||||
foreach ($idsByType as $type => $ids) {
|
||||
foreach (array_chunk($ids, 1000) as $idChunk) {
|
||||
DB::table('entity_permissions_collapsed')
|
||||
->where('entity_type', '=', $type)
|
||||
->whereIn('entity_id', $idChunk)
|
||||
->delete();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given list of entities into "SimpleEntityData" representations
|
||||
* for faster usage and property access.
|
||||
*
|
||||
* @param Entity[] $entities
|
||||
*
|
||||
* @return SimpleEntityData[]
|
||||
*/
|
||||
protected function entitiesToSimpleEntities(array $entities): array
|
||||
{
|
||||
$simpleEntities = [];
|
||||
|
||||
foreach ($entities as $entity) {
|
||||
$attrs = $entity->getAttributes();
|
||||
$simple = new SimpleEntityData();
|
||||
$simple->id = $attrs['id'];
|
||||
$simple->type = $entity->getMorphClass();
|
||||
$simple->book_id = $attrs['book_id'] ?? null;
|
||||
$simple->chapter_id = $attrs['chapter_id'] ?? null;
|
||||
$simpleEntities[] = $simple;
|
||||
}
|
||||
|
||||
return $simpleEntities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create & Save collapsed entity permissions.
|
||||
*
|
||||
* @param Entity[] $originalEntities
|
||||
*/
|
||||
protected function generateCollapsedPermissions(array $originalEntities)
|
||||
{
|
||||
$entities = $this->entitiesToSimpleEntities($originalEntities);
|
||||
$collapsedPermData = [];
|
||||
|
||||
// Fetch related entity permissions
|
||||
$permissions = $this->getEntityPermissionsForEntities($entities);
|
||||
|
||||
// Create a mapping of explicit entity permissions
|
||||
$permissionMap = new EntityPermissionMap($permissions);
|
||||
|
||||
// Create Joint Permission Data
|
||||
foreach ($entities as $entity) {
|
||||
array_push($collapsedPermData, ...$this->createCollapsedPermissionData($entity, $permissionMap));
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($collapsedPermData) {
|
||||
foreach (array_chunk($collapsedPermData, 1000) as $dataChunk) {
|
||||
DB::table('entity_permissions_collapsed')->insert($dataChunk);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create collapsed permission data for the given entity using the given permission map.
|
||||
*/
|
||||
protected function createCollapsedPermissionData(SimpleEntityData $entity, EntityPermissionMap $permissionMap): array
|
||||
{
|
||||
$chain = [
|
||||
$entity->type . ':' . $entity->id,
|
||||
$entity->chapter_id ? ('chapter:' . $entity->chapter_id) : null,
|
||||
$entity->book_id ? ('book:' . $entity->book_id) : null,
|
||||
];
|
||||
|
||||
$permissionData = [];
|
||||
$overridesApplied = [];
|
||||
|
||||
foreach ($chain as $entityTypeId) {
|
||||
if ($entityTypeId === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$permissions = $permissionMap->getForEntity($entityTypeId);
|
||||
foreach ($permissions as $permission) {
|
||||
$related = $permission->getAssignedType() . ':' . $permission->getAssignedTypeId();
|
||||
if (!isset($overridesApplied[$related])) {
|
||||
$permissionData[] = [
|
||||
'role_id' => $permission->role_id,
|
||||
'user_id' => $permission->user_id,
|
||||
'view' => $permission->view,
|
||||
'entity_type' => $entity->type,
|
||||
'entity_id' => $entity->id,
|
||||
];
|
||||
$overridesApplied[$related] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $permissionData;
|
||||
}
|
||||
|
||||
/**
|
||||
* From the given entity list, provide back a mapping of entity types to
|
||||
* the ids of that given type. The type used is the DB morph class.
|
||||
*
|
||||
* @param SimpleEntityData[] $entities
|
||||
*
|
||||
* @return array<string, int[]>
|
||||
*/
|
||||
protected function entitiesToTypeIdMap(array $entities): array
|
||||
{
|
||||
$idsByType = [];
|
||||
|
||||
foreach ($entities as $entity) {
|
||||
if (!isset($idsByType[$entity->type])) {
|
||||
$idsByType[$entity->type] = [];
|
||||
}
|
||||
|
||||
$idsByType[$entity->type][] = $entity->id;
|
||||
}
|
||||
|
||||
return $idsByType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entity permissions for all the given entities.
|
||||
*
|
||||
* @param SimpleEntityData[] $entities
|
||||
*
|
||||
* @return EntityPermission[]
|
||||
*/
|
||||
protected function getEntityPermissionsForEntities(array $entities): array
|
||||
{
|
||||
$idsByType = $this->entitiesToTypeIdMap($entities);
|
||||
$permissionFetch = EntityPermission::query()
|
||||
->where(function (Builder $query) use ($idsByType) {
|
||||
foreach ($idsByType as $type => $ids) {
|
||||
$query->orWhere(function (Builder $query) use ($type, $ids) {
|
||||
$query->where('entity_type', '=', $type)->whereIn('entity_id', $ids);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return $permissionFetch->get()->all();
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,14 @@
|
||||
namespace BookStack\Auth\Permissions;
|
||||
|
||||
use BookStack\Auth\Role;
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $role_id
|
||||
* @property int $user_id
|
||||
* @property int $entity_id
|
||||
* @property string $entity_type
|
||||
* @property boolean $view
|
||||
@@ -21,17 +22,9 @@ class EntityPermission extends Model
|
||||
{
|
||||
public const PERMISSIONS = ['view', 'create', 'update', 'delete'];
|
||||
|
||||
protected $fillable = ['role_id', 'view', 'create', 'update', 'delete'];
|
||||
protected $fillable = ['role_id', 'user_id', 'view', 'create', 'update', 'delete'];
|
||||
public $timestamps = false;
|
||||
|
||||
/**
|
||||
* Get this restriction's attached entity.
|
||||
*/
|
||||
public function restrictable(): MorphTo
|
||||
{
|
||||
return $this->morphTo('restrictable');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the role assigned to this entity permission.
|
||||
*/
|
||||
@@ -39,4 +32,38 @@ class EntityPermission extends Model
|
||||
{
|
||||
return $this->belongsTo(Role::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user assigned to this entity permission.
|
||||
*/
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type of entity permission this is.
|
||||
* Will be one of: user, role, fallback
|
||||
*/
|
||||
public function getAssignedType(): string
|
||||
{
|
||||
if ($this->user_id) {
|
||||
return 'user';
|
||||
}
|
||||
|
||||
if ($this->role_id) {
|
||||
return 'role';
|
||||
}
|
||||
|
||||
return 'fallback';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ID for the assigned type of permission.
|
||||
* (Role/User ID). Defaults to 0 for fallback.
|
||||
*/
|
||||
public function getAssignedTypeId(): int
|
||||
{
|
||||
return $this->user_id ?? $this->role_id ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
37
app/Auth/Permissions/EntityPermissionMap.php
Normal file
37
app/Auth/Permissions/EntityPermissionMap.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Auth\Permissions;
|
||||
|
||||
class EntityPermissionMap
|
||||
{
|
||||
protected array $map = [];
|
||||
|
||||
/**
|
||||
* @param EntityPermission[] $permissions
|
||||
*/
|
||||
public function __construct(array $permissions = [])
|
||||
{
|
||||
foreach ($permissions as $entityPermission) {
|
||||
$this->addPermission($entityPermission);
|
||||
}
|
||||
}
|
||||
|
||||
protected function addPermission(EntityPermission $permission)
|
||||
{
|
||||
$entityCombinedId = $permission->entity_type . ':' . $permission->entity_id;
|
||||
|
||||
if (!isset($this->map[$entityCombinedId])) {
|
||||
$this->map[$entityCombinedId] = [];
|
||||
}
|
||||
|
||||
$this->map[$entityCombinedId][] = $permission;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return EntityPermission[]
|
||||
*/
|
||||
public function getForEntity(string $typeIdString): array
|
||||
{
|
||||
return $this->map[$typeIdString] ?? [];
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Auth\Permissions;
|
||||
|
||||
use BookStack\Auth\Role;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphOne;
|
||||
|
||||
class JointPermission extends Model
|
||||
{
|
||||
protected $primaryKey = null;
|
||||
public $timestamps = false;
|
||||
|
||||
/**
|
||||
* Get the role that this points to.
|
||||
*/
|
||||
public function role(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Role::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entity this points to.
|
||||
*/
|
||||
public function entity(): MorphOne
|
||||
{
|
||||
return $this->morphOne(Entity::class, 'entity');
|
||||
}
|
||||
}
|
||||
@@ -1,408 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Auth\Permissions;
|
||||
|
||||
use BookStack\Auth\Role;
|
||||
use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Models\BookChild;
|
||||
use BookStack\Entities\Models\Bookshelf;
|
||||
use BookStack\Entities\Models\Chapter;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Joint permissions provide a pre-query "cached" table of view permissions for all core entity
|
||||
* types for all roles in the system. This class generates out that table for different scenarios.
|
||||
*/
|
||||
class JointPermissionBuilder
|
||||
{
|
||||
/**
|
||||
* @var array<string, array<int, SimpleEntityData>>
|
||||
*/
|
||||
protected array $entityCache;
|
||||
|
||||
/**
|
||||
* Re-generate all entity permission from scratch.
|
||||
*/
|
||||
public function rebuildForAll()
|
||||
{
|
||||
JointPermission::query()->truncate();
|
||||
|
||||
// Get all roles (Should be the most limited dimension)
|
||||
$roles = Role::query()->with('permissions')->get()->all();
|
||||
|
||||
// Chunk through all books
|
||||
$this->bookFetchQuery()->chunk(5, function (EloquentCollection $books) use ($roles) {
|
||||
$this->buildJointPermissionsForBooks($books, $roles);
|
||||
});
|
||||
|
||||
// Chunk through all bookshelves
|
||||
Bookshelf::query()->withTrashed()->select(['id', 'owned_by'])
|
||||
->chunk(50, function (EloquentCollection $shelves) use ($roles) {
|
||||
$this->createManyJointPermissions($shelves->all(), $roles);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild the entity jointPermissions for a particular entity.
|
||||
*/
|
||||
public function rebuildForEntity(Entity $entity)
|
||||
{
|
||||
$entities = [$entity];
|
||||
if ($entity instanceof Book) {
|
||||
$books = $this->bookFetchQuery()->where('id', '=', $entity->id)->get();
|
||||
$this->buildJointPermissionsForBooks($books, Role::query()->with('permissions')->get()->all(), true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var BookChild $entity */
|
||||
if ($entity->book) {
|
||||
$entities[] = $entity->book;
|
||||
}
|
||||
|
||||
if ($entity instanceof Page && $entity->chapter_id) {
|
||||
$entities[] = $entity->chapter;
|
||||
}
|
||||
|
||||
if ($entity instanceof Chapter) {
|
||||
foreach ($entity->pages as $page) {
|
||||
$entities[] = $page;
|
||||
}
|
||||
}
|
||||
|
||||
$this->buildJointPermissionsForEntities($entities);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the entity jointPermissions for a particular role.
|
||||
*/
|
||||
public function rebuildForRole(Role $role)
|
||||
{
|
||||
$roles = [$role];
|
||||
$role->jointPermissions()->delete();
|
||||
$role->load('permissions');
|
||||
|
||||
// Chunk through all books
|
||||
$this->bookFetchQuery()->chunk(20, function ($books) use ($roles) {
|
||||
$this->buildJointPermissionsForBooks($books, $roles);
|
||||
});
|
||||
|
||||
// Chunk through all bookshelves
|
||||
Bookshelf::query()->select(['id', 'owned_by'])
|
||||
->chunk(50, function ($shelves) use ($roles) {
|
||||
$this->createManyJointPermissions($shelves->all(), $roles);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the local entity cache and ensure it's empty.
|
||||
*
|
||||
* @param SimpleEntityData[] $entities
|
||||
*/
|
||||
protected function readyEntityCache(array $entities)
|
||||
{
|
||||
$this->entityCache = [];
|
||||
|
||||
foreach ($entities as $entity) {
|
||||
if (!isset($this->entityCache[$entity->type])) {
|
||||
$this->entityCache[$entity->type] = [];
|
||||
}
|
||||
|
||||
$this->entityCache[$entity->type][$entity->id] = $entity;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a book via ID, Checks local cache.
|
||||
*/
|
||||
protected function getBook(int $bookId): SimpleEntityData
|
||||
{
|
||||
return $this->entityCache['book'][$bookId];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a chapter via ID, Checks local cache.
|
||||
*/
|
||||
protected function getChapter(int $chapterId): SimpleEntityData
|
||||
{
|
||||
return $this->entityCache['chapter'][$chapterId];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a query for fetching a book with its children.
|
||||
*/
|
||||
protected function bookFetchQuery(): Builder
|
||||
{
|
||||
return Book::query()->withTrashed()
|
||||
->select(['id', 'owned_by'])->with([
|
||||
'chapters' => function ($query) {
|
||||
$query->withTrashed()->select(['id', 'owned_by', 'book_id']);
|
||||
},
|
||||
'pages' => function ($query) {
|
||||
$query->withTrashed()->select(['id', 'owned_by', 'book_id', 'chapter_id']);
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build joint permissions for the given book and role combinations.
|
||||
*/
|
||||
protected function buildJointPermissionsForBooks(EloquentCollection $books, array $roles, bool $deleteOld = false)
|
||||
{
|
||||
$entities = clone $books;
|
||||
|
||||
/** @var Book $book */
|
||||
foreach ($books->all() as $book) {
|
||||
foreach ($book->getRelation('chapters') as $chapter) {
|
||||
$entities->push($chapter);
|
||||
}
|
||||
foreach ($book->getRelation('pages') as $page) {
|
||||
$entities->push($page);
|
||||
}
|
||||
}
|
||||
|
||||
if ($deleteOld) {
|
||||
$this->deleteManyJointPermissionsForEntities($entities->all());
|
||||
}
|
||||
|
||||
$this->createManyJointPermissions($entities->all(), $roles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild the entity jointPermissions for a collection of entities.
|
||||
*/
|
||||
protected function buildJointPermissionsForEntities(array $entities)
|
||||
{
|
||||
$roles = Role::query()->get()->values()->all();
|
||||
$this->deleteManyJointPermissionsForEntities($entities);
|
||||
$this->createManyJointPermissions($entities, $roles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all the entity jointPermissions for a list of entities.
|
||||
*
|
||||
* @param Entity[] $entities
|
||||
*/
|
||||
protected function deleteManyJointPermissionsForEntities(array $entities)
|
||||
{
|
||||
$simpleEntities = $this->entitiesToSimpleEntities($entities);
|
||||
$idsByType = $this->entitiesToTypeIdMap($simpleEntities);
|
||||
|
||||
DB::transaction(function () use ($idsByType) {
|
||||
foreach ($idsByType as $type => $ids) {
|
||||
foreach (array_chunk($ids, 1000) as $idChunk) {
|
||||
DB::table('joint_permissions')
|
||||
->where('entity_type', '=', $type)
|
||||
->whereIn('entity_id', $idChunk)
|
||||
->delete();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Entity[] $entities
|
||||
*
|
||||
* @return SimpleEntityData[]
|
||||
*/
|
||||
protected function entitiesToSimpleEntities(array $entities): array
|
||||
{
|
||||
$simpleEntities = [];
|
||||
|
||||
foreach ($entities as $entity) {
|
||||
$attrs = $entity->getAttributes();
|
||||
$simple = new SimpleEntityData();
|
||||
$simple->id = $attrs['id'];
|
||||
$simple->type = $entity->getMorphClass();
|
||||
$simple->owned_by = $attrs['owned_by'] ?? 0;
|
||||
$simple->book_id = $attrs['book_id'] ?? null;
|
||||
$simple->chapter_id = $attrs['chapter_id'] ?? null;
|
||||
$simpleEntities[] = $simple;
|
||||
}
|
||||
|
||||
return $simpleEntities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create & Save entity jointPermissions for many entities and roles.
|
||||
*
|
||||
* @param Entity[] $originalEntities
|
||||
* @param Role[] $roles
|
||||
*/
|
||||
protected function createManyJointPermissions(array $originalEntities, array $roles)
|
||||
{
|
||||
$entities = $this->entitiesToSimpleEntities($originalEntities);
|
||||
$this->readyEntityCache($entities);
|
||||
$jointPermissions = [];
|
||||
|
||||
// Fetch related entity permissions
|
||||
$permissions = $this->getEntityPermissionsForEntities($entities);
|
||||
|
||||
// Create a mapping of explicit entity permissions
|
||||
$permissionMap = [];
|
||||
foreach ($permissions as $permission) {
|
||||
$key = $permission->entity_type . ':' . $permission->entity_id . ':' . $permission->role_id;
|
||||
$permissionMap[$key] = $permission->view;
|
||||
}
|
||||
|
||||
// Create a mapping of role permissions
|
||||
$rolePermissionMap = [];
|
||||
foreach ($roles as $role) {
|
||||
foreach ($role->permissions as $permission) {
|
||||
$rolePermissionMap[$role->getRawAttribute('id') . ':' . $permission->getRawAttribute('name')] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Create Joint Permission Data
|
||||
foreach ($entities as $entity) {
|
||||
foreach ($roles as $role) {
|
||||
$jointPermissions[] = $this->createJointPermissionData(
|
||||
$entity,
|
||||
$role->getRawAttribute('id'),
|
||||
$permissionMap,
|
||||
$rolePermissionMap,
|
||||
$role->system_name === 'admin'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($jointPermissions) {
|
||||
foreach (array_chunk($jointPermissions, 1000) as $jointPermissionChunk) {
|
||||
DB::table('joint_permissions')->insert($jointPermissionChunk);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* From the given entity list, provide back a mapping of entity types to
|
||||
* the ids of that given type. The type used is the DB morph class.
|
||||
*
|
||||
* @param SimpleEntityData[] $entities
|
||||
*
|
||||
* @return array<string, int[]>
|
||||
*/
|
||||
protected function entitiesToTypeIdMap(array $entities): array
|
||||
{
|
||||
$idsByType = [];
|
||||
|
||||
foreach ($entities as $entity) {
|
||||
if (!isset($idsByType[$entity->type])) {
|
||||
$idsByType[$entity->type] = [];
|
||||
}
|
||||
|
||||
$idsByType[$entity->type][] = $entity->id;
|
||||
}
|
||||
|
||||
return $idsByType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entity permissions for all the given entities.
|
||||
*
|
||||
* @param SimpleEntityData[] $entities
|
||||
*
|
||||
* @return EntityPermission[]
|
||||
*/
|
||||
protected function getEntityPermissionsForEntities(array $entities): array
|
||||
{
|
||||
$idsByType = $this->entitiesToTypeIdMap($entities);
|
||||
$permissionFetch = EntityPermission::query()
|
||||
->where(function (Builder $query) use ($idsByType) {
|
||||
foreach ($idsByType as $type => $ids) {
|
||||
$query->orWhere(function (Builder $query) use ($type, $ids) {
|
||||
$query->where('entity_type', '=', $type)->whereIn('entity_id', $ids);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return $permissionFetch->get()->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create entity permission data for an entity and role
|
||||
* for a particular action.
|
||||
*/
|
||||
protected function createJointPermissionData(SimpleEntityData $entity, int $roleId, array $permissionMap, array $rolePermissionMap, bool $isAdminRole): array
|
||||
{
|
||||
$permissionPrefix = $entity->type . '-view';
|
||||
$roleHasPermission = isset($rolePermissionMap[$roleId . ':' . $permissionPrefix . '-all']);
|
||||
$roleHasPermissionOwn = isset($rolePermissionMap[$roleId . ':' . $permissionPrefix . '-own']);
|
||||
|
||||
if ($isAdminRole) {
|
||||
return $this->createJointPermissionDataArray($entity, $roleId, true, true);
|
||||
}
|
||||
|
||||
if ($this->entityPermissionsActiveForRole($permissionMap, $entity, $roleId)) {
|
||||
$hasAccess = $this->mapHasActiveRestriction($permissionMap, $entity, $roleId);
|
||||
|
||||
return $this->createJointPermissionDataArray($entity, $roleId, $hasAccess, $hasAccess);
|
||||
}
|
||||
|
||||
if ($entity->type === 'book' || $entity->type === 'bookshelf') {
|
||||
return $this->createJointPermissionDataArray($entity, $roleId, $roleHasPermission, $roleHasPermissionOwn);
|
||||
}
|
||||
|
||||
// For chapters and pages, Check if explicit permissions are set on the Book.
|
||||
$book = $this->getBook($entity->book_id);
|
||||
$hasExplicitAccessToParents = $this->mapHasActiveRestriction($permissionMap, $book, $roleId);
|
||||
$hasPermissiveAccessToParents = !$this->entityPermissionsActiveForRole($permissionMap, $book, $roleId);
|
||||
|
||||
// For pages with a chapter, Check if explicit permissions are set on the Chapter
|
||||
if ($entity->type === 'page' && $entity->chapter_id !== 0) {
|
||||
$chapter = $this->getChapter($entity->chapter_id);
|
||||
$chapterRestricted = $this->entityPermissionsActiveForRole($permissionMap, $chapter, $roleId);
|
||||
$hasPermissiveAccessToParents = $hasPermissiveAccessToParents && !$chapterRestricted;
|
||||
if ($chapterRestricted) {
|
||||
$hasExplicitAccessToParents = $this->mapHasActiveRestriction($permissionMap, $chapter, $roleId);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->createJointPermissionDataArray(
|
||||
$entity,
|
||||
$roleId,
|
||||
($hasExplicitAccessToParents || ($roleHasPermission && $hasPermissiveAccessToParents)),
|
||||
($hasExplicitAccessToParents || ($roleHasPermissionOwn && $hasPermissiveAccessToParents))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if entity permissions are defined within the given map, for the given entity and role.
|
||||
* Checks for the default `role_id=0` backup option as a fallback.
|
||||
*/
|
||||
protected function entityPermissionsActiveForRole(array $permissionMap, SimpleEntityData $entity, int $roleId): bool
|
||||
{
|
||||
$keyPrefix = $entity->type . ':' . $entity->id . ':';
|
||||
return isset($permissionMap[$keyPrefix . $roleId]) || isset($permissionMap[$keyPrefix . '0']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for an active restriction in an entity map.
|
||||
*/
|
||||
protected function mapHasActiveRestriction(array $entityMap, SimpleEntityData $entity, int $roleId): bool
|
||||
{
|
||||
$roleKey = $entity->type . ':' . $entity->id . ':' . $roleId;
|
||||
$defaultKey = $entity->type . ':' . $entity->id . ':0';
|
||||
|
||||
return $entityMap[$roleKey] ?? $entityMap[$defaultKey] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an array of data with the information of an entity jointPermissions.
|
||||
* Used to build data for bulk insertion.
|
||||
*/
|
||||
protected function createJointPermissionDataArray(SimpleEntityData $entity, int $roleId, bool $permissionAll, bool $permissionOwn): array
|
||||
{
|
||||
return [
|
||||
'entity_id' => $entity->id,
|
||||
'entity_type' => $entity->type,
|
||||
'has_permission' => $permissionAll,
|
||||
'has_permission_own' => $permissionOwn,
|
||||
'owned_by' => $entity->owned_by,
|
||||
'role_id' => $roleId,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ use BookStack\Traits\HasCreatorAndUpdater;
|
||||
use BookStack\Traits\HasOwner;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Query\Builder as QueryBuilder;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class PermissionApplicator
|
||||
@@ -48,7 +50,7 @@ class PermissionApplicator
|
||||
return $hasRolePermission;
|
||||
}
|
||||
|
||||
$hasApplicableEntityPermissions = $this->hasEntityPermission($ownable, $userRoleIds, $action);
|
||||
$hasApplicableEntityPermissions = $this->hasEntityPermission($ownable, $userRoleIds, $user->id, $action);
|
||||
|
||||
return is_null($hasApplicableEntityPermissions) ? $hasRolePermission : $hasApplicableEntityPermissions;
|
||||
}
|
||||
@@ -57,7 +59,7 @@ class PermissionApplicator
|
||||
* Check if there are permissions that are applicable for the given entity item, action and roles.
|
||||
* Returns null when no entity permissions are in force.
|
||||
*/
|
||||
protected function hasEntityPermission(Entity $entity, array $userRoleIds, string $action): ?bool
|
||||
protected function hasEntityPermission(Entity $entity, array $userRoleIds, int $userId, string $action): ?bool
|
||||
{
|
||||
$this->ensureValidEntityAction($action);
|
||||
|
||||
@@ -66,38 +68,63 @@ class PermissionApplicator
|
||||
return true;
|
||||
}
|
||||
|
||||
// The chain order here is very important due to the fact we walk up the chain
|
||||
// in the loop below. Earlier items in the chain have higher priority.
|
||||
$chain = [$entity];
|
||||
// The array order here is very important due to the fact we walk up the chain
|
||||
// in the flattening loop below. Earlier items in the chain have higher priority.
|
||||
$typeIdList = [$entity->getMorphClass() . ':' . $entity->id];
|
||||
if ($entity instanceof Page && $entity->chapter_id) {
|
||||
$chain[] = $entity->chapter;
|
||||
$typeIdList[] = 'chapter:' . $entity->chapter_id;
|
||||
}
|
||||
|
||||
if ($entity instanceof Page || $entity instanceof Chapter) {
|
||||
$chain[] = $entity->book;
|
||||
$typeIdList[] = 'book:' . $entity->book_id;
|
||||
}
|
||||
|
||||
foreach ($chain as $currentEntity) {
|
||||
$allowedByRoleId = $currentEntity->permissions()
|
||||
->whereIn('role_id', [0, ...$userRoleIds])
|
||||
->pluck($action, 'role_id');
|
||||
$relevantPermissions = EntityPermission::query()
|
||||
->where(function (Builder $query) use ($typeIdList) {
|
||||
foreach ($typeIdList as $typeId) {
|
||||
$query->orWhere(function (Builder $query) use ($typeId) {
|
||||
[$type, $id] = explode(':', $typeId);
|
||||
$query->where('entity_type', '=', $type)
|
||||
->where('entity_id', '=', $id);
|
||||
});
|
||||
}
|
||||
})->where(function (Builder $query) use ($userRoleIds, $userId) {
|
||||
$query->whereIn('role_id', $userRoleIds)
|
||||
->orWhere('user_id', '=', $userId)
|
||||
->orWhere(function (Builder $query) {
|
||||
$query->whereNull(['role_id', 'user_id']);
|
||||
});
|
||||
})->get(['entity_id', 'entity_type', 'role_id', 'user_id', $action])
|
||||
->all();
|
||||
|
||||
// Continue up the chain if no applicable entity permission overrides.
|
||||
if ($allowedByRoleId->isEmpty()) {
|
||||
continue;
|
||||
$permissionMap = new EntityPermissionMap($relevantPermissions);
|
||||
$permitsByType = ['user' => [], 'fallback' => [], 'role' => []];
|
||||
|
||||
// Collapse and simplify permission structure
|
||||
foreach ($typeIdList as $typeId) {
|
||||
$permissions = $permissionMap->getForEntity($typeId);
|
||||
foreach ($permissions as $permission) {
|
||||
$related = $permission->getAssignedType();
|
||||
$relatedId = $permission->getAssignedTypeId();
|
||||
if (!isset($permitsByType[$related][$relatedId])) {
|
||||
$permitsByType[$related][$relatedId] = $permission->$action;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we have user-role-specific permissions set, allow if any of those
|
||||
// role permissions allow access.
|
||||
$hasDefault = $allowedByRoleId->has(0);
|
||||
if (!$hasDefault || $allowedByRoleId->count() > 1) {
|
||||
return $allowedByRoleId->search(function (bool $allowed, int $roleId) {
|
||||
return $roleId !== 0 && $allowed;
|
||||
}) !== false;
|
||||
}
|
||||
// Return user-level permission if exists
|
||||
if (count($permitsByType['user']) > 0) {
|
||||
return boolval(array_values($permitsByType['user'])[0]);
|
||||
}
|
||||
|
||||
// Otherwise, return the default "Other roles" fallback value.
|
||||
return $allowedByRoleId->get(0);
|
||||
// Return grant or reject from role-level if exists
|
||||
if (count($permitsByType['role']) > 0) {
|
||||
return boolval(max($permitsByType['role']));
|
||||
}
|
||||
|
||||
// Return fallback permission if exists
|
||||
if (count($permitsByType['fallback']) > 0) {
|
||||
return boolval($permitsByType['fallback'][0]);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -113,7 +140,10 @@ class PermissionApplicator
|
||||
|
||||
$permissionQuery = EntityPermission::query()
|
||||
->where($action, '=', true)
|
||||
->whereIn('role_id', $this->getCurrentUserRoleIds());
|
||||
->where(function (Builder $query) {
|
||||
$query->whereIn('role_id', $this->getCurrentUserRoleIds())
|
||||
->orWhere('user_id', '=', $this->currentUser()->id);
|
||||
});
|
||||
|
||||
if (!empty($entityClass)) {
|
||||
/** @var Entity $entityInstance */
|
||||
@@ -130,18 +160,140 @@ class PermissionApplicator
|
||||
* Limit the given entity query so that the query will only
|
||||
* return items that the user has view permission for.
|
||||
*/
|
||||
public function restrictEntityQuery(Builder $query): Builder
|
||||
public function restrictEntityQuery(Builder $query, string $morphClass): Builder
|
||||
{
|
||||
return $query->where(function (Builder $parentQuery) {
|
||||
$parentQuery->whereHas('jointPermissions', function (Builder $permissionQuery) {
|
||||
$permissionQuery->whereIn('role_id', $this->getCurrentUserRoleIds())
|
||||
->where(function (Builder $query) {
|
||||
$this->addJointHasPermissionCheck($query, $this->currentUser()->id);
|
||||
});
|
||||
});
|
||||
$this->applyPermissionsToQuery($query, $query->getModel()->getTable(), $morphClass, 'id', '');
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Builder|QueryBuilder $query
|
||||
*/
|
||||
protected function applyPermissionsToQuery($query, string $queryTable, string $entityTypeLimiter, string $entityIdColumn, string $entityTypeColumn): void
|
||||
{
|
||||
if ($this->currentUser()->hasSystemRole('admin')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->applyFallbackJoin($query, $queryTable, $entityTypeLimiter, $entityIdColumn, $entityTypeColumn);
|
||||
$this->applyRoleJoin($query, $queryTable, $entityTypeLimiter, $entityIdColumn, $entityTypeColumn);
|
||||
$this->applyUserJoin($query, $queryTable, $entityTypeLimiter, $entityIdColumn, $entityTypeColumn);
|
||||
$this->applyPermissionWhereFilter($query, $queryTable, $entityTypeLimiter, $entityTypeColumn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the where condition to a permission restricting query, to limit based upon the values of the joined
|
||||
* permission data. Query must have joins pre-applied.
|
||||
* Either entityTypeLimiter or entityTypeColumn should be supplied, with the other empty.
|
||||
* Both should not be applied since that would conflict upon intent.
|
||||
* @param Builder|QueryBuilder $query
|
||||
*/
|
||||
protected function applyPermissionWhereFilter($query, string $queryTable, string $entityTypeLimiter, string $entityTypeColumn)
|
||||
{
|
||||
$abilities = ['all' => [], 'own' => []];
|
||||
$types = $entityTypeLimiter ? [$entityTypeLimiter] : ['page', 'chapter', 'bookshelf', 'book'];
|
||||
$fullEntityTypeColumn = $queryTable . '.' . $entityTypeColumn;
|
||||
foreach ($types as $type) {
|
||||
$abilities['all'][$type] = userCan($type . '-view-all');
|
||||
$abilities['own'][$type] = userCan($type . '-view-own');
|
||||
}
|
||||
|
||||
$abilities['all'] = array_filter($abilities['all']);
|
||||
$abilities['own'] = array_filter($abilities['own']);
|
||||
|
||||
$query->where(function (Builder $query) use ($abilities, $fullEntityTypeColumn, $entityTypeColumn) {
|
||||
$query->where('perms_user', '=', 1)
|
||||
->orWhere(function (Builder $query) {
|
||||
$query->whereNull('perms_user')->where('perms_role', '=', 1);
|
||||
})->orWhere(function (Builder $query) {
|
||||
$query->whereNull(['perms_user', 'perms_role'])
|
||||
->where('perms_fallback', '=', 1);
|
||||
});
|
||||
|
||||
if (count($abilities['all']) > 0) {
|
||||
$query->orWhere(function (Builder $query) use ($abilities, $fullEntityTypeColumn, $entityTypeColumn) {
|
||||
$query->whereNull(['perms_user', 'perms_role', 'perms_fallback']);
|
||||
if ($entityTypeColumn) {
|
||||
$query->whereIn($fullEntityTypeColumn, array_keys($abilities['all']));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (count($abilities['own']) > 0) {
|
||||
$query->orWhere(function (Builder $query) use ($abilities, $fullEntityTypeColumn, $entityTypeColumn) {
|
||||
$query->whereNull(['perms_user', 'perms_role', 'perms_fallback'])
|
||||
->where('owned_by', '=', $this->currentUser()->id);
|
||||
if ($entityTypeColumn) {
|
||||
$query->whereIn($fullEntityTypeColumn, array_keys($abilities['all']));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Builder|QueryBuilder $query
|
||||
*/
|
||||
protected function applyPermissionJoin(callable $joinCallable, string $subAlias, $query, string $queryTable, string $entityTypeLimiter, string $entityIdColumn, string $entityTypeColumn)
|
||||
{
|
||||
$joinCondition = $this->getJoinCondition($queryTable, $subAlias, $entityIdColumn, $entityTypeColumn);
|
||||
|
||||
$query->joinSub(function (QueryBuilder $joinQuery) use ($joinCallable, $entityTypeLimiter) {
|
||||
$joinQuery->select(['entity_id', 'entity_type'])->from('entity_permissions_collapsed')
|
||||
->groupBy('entity_id', 'entity_type');
|
||||
$joinCallable($joinQuery);
|
||||
|
||||
if ($entityTypeLimiter) {
|
||||
$joinQuery->where('entity_type', '=', $entityTypeLimiter);
|
||||
}
|
||||
}, $subAlias, $joinCondition, null, null, 'left');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Builder|QueryBuilder $query
|
||||
*/
|
||||
protected function applyUserJoin($query, string $queryTable, string $entityTypeLimiter, string $entityIdColumn, string $entityTypeColumn)
|
||||
{
|
||||
$this->applyPermissionJoin(function (QueryBuilder $joinQuery) {
|
||||
$joinQuery->selectRaw('max(view) as perms_user')
|
||||
->where('user_id', '=', $this->currentUser()->id);
|
||||
}, 'p_u', $query, $queryTable, $entityTypeLimiter, $entityIdColumn, $entityTypeColumn);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Builder|QueryBuilder $query
|
||||
*/
|
||||
protected function applyRoleJoin($query, string $queryTable, string $entityTypeLimiter, string $entityIdColumn, string $entityTypeColumn)
|
||||
{
|
||||
$this->applyPermissionJoin(function (QueryBuilder $joinQuery) {
|
||||
$joinQuery->selectRaw('max(view) as perms_role')
|
||||
->whereIn('role_id', $this->getCurrentUserRoleIds());
|
||||
}, 'p_r', $query, $queryTable, $entityTypeLimiter, $entityIdColumn, $entityTypeColumn);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Builder|QueryBuilder $query
|
||||
*/
|
||||
protected function applyFallbackJoin($query, string $queryTable, string $entityTypeLimiter, string $entityIdColumn, string $entityTypeColumn)
|
||||
{
|
||||
$this->applyPermissionJoin(function (QueryBuilder $joinQuery) {
|
||||
$joinQuery->selectRaw('max(view) as perms_fallback')
|
||||
->whereNull(['role_id', 'user_id']);
|
||||
}, 'p_f', $query, $queryTable, $entityTypeLimiter, $entityIdColumn, $entityTypeColumn);
|
||||
}
|
||||
|
||||
protected function getJoinCondition(string $queryTable, string $joinTableName, string $entityIdColumn, string $entityTypeColumn): callable
|
||||
{
|
||||
return function (JoinClause $join) use ($queryTable, $joinTableName, $entityIdColumn, $entityTypeColumn) {
|
||||
$join->on($queryTable . '.' . $entityIdColumn, '=', $joinTableName . '.entity_id');
|
||||
if ($entityTypeColumn) {
|
||||
$join->on($queryTable . '.' . $entityTypeColumn, '=', $joinTableName . '.entity_type');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend the given page query to ensure draft items are not visible
|
||||
* unless created by the given user.
|
||||
@@ -166,30 +318,23 @@ class PermissionApplicator
|
||||
*/
|
||||
public function restrictEntityRelationQuery($query, string $tableName, string $entityIdColumn, string $entityTypeColumn)
|
||||
{
|
||||
$tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn, 'entityTypeColumn' => $entityTypeColumn];
|
||||
$pageMorphClass = (new Page())->getMorphClass();
|
||||
|
||||
$q = $query->whereExists(function ($permissionQuery) use (&$tableDetails) {
|
||||
/** @var Builder $permissionQuery */
|
||||
$permissionQuery->select(['role_id'])->from('joint_permissions')
|
||||
->whereColumn('joint_permissions.entity_id', '=', $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
|
||||
->whereColumn('joint_permissions.entity_type', '=', $tableDetails['tableName'] . '.' . $tableDetails['entityTypeColumn'])
|
||||
->whereIn('joint_permissions.role_id', $this->getCurrentUserRoleIds())
|
||||
->where(function (QueryBuilder $query) {
|
||||
$this->addJointHasPermissionCheck($query, $this->currentUser()->id);
|
||||
});
|
||||
})->where(function ($query) use ($tableDetails, $pageMorphClass) {
|
||||
/** @var Builder $query */
|
||||
$query->where($tableDetails['entityTypeColumn'], '!=', $pageMorphClass)
|
||||
->orWhereExists(function (QueryBuilder $query) use ($tableDetails, $pageMorphClass) {
|
||||
$query->select('id')->from('pages')
|
||||
->whereColumn('pages.id', '=', $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
|
||||
->where($tableDetails['tableName'] . '.' . $tableDetails['entityTypeColumn'], '=', $pageMorphClass)
|
||||
->where('pages.draft', '=', false);
|
||||
$query->leftJoinSub(function (QueryBuilder $query) {
|
||||
$query->select(['id as entity_id', DB::raw("'page' as entity_type"), 'owned_by', 'deleted_at', 'draft'])->from('pages');
|
||||
$tablesByType = ['page' => 'pages', 'book' => 'books', 'chapter' => 'chapters', 'bookshelf' => 'bookshelves'];
|
||||
foreach ($tablesByType as $type => $table) {
|
||||
$query->unionAll(function (QueryBuilder $query) use ($type, $table) {
|
||||
$query->select(['id as entity_id', DB::raw("'{$type}' as entity_type"), 'owned_by', 'deleted_at', DB::raw('0 as draft')])->from($table);
|
||||
});
|
||||
}
|
||||
}, 'entities', function (JoinClause $join) use ($tableName, $entityIdColumn, $entityTypeColumn) {
|
||||
$join->on($tableName . '.' . $entityIdColumn, '=', 'entities.entity_id')
|
||||
->on($tableName . '.' . $entityTypeColumn, '=', 'entities.entity_type');
|
||||
});
|
||||
|
||||
return $q;
|
||||
$this->applyPermissionsToQuery($query, $tableName, '', $entityIdColumn, $entityTypeColumn);
|
||||
// TODO - Test page draft access (Might allow drafts which should not be seen)
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -200,50 +345,12 @@ class PermissionApplicator
|
||||
*/
|
||||
public function restrictPageRelationQuery(Builder $query, string $tableName, string $pageIdColumn): Builder
|
||||
{
|
||||
$fullPageIdColumn = $tableName . '.' . $pageIdColumn;
|
||||
$morphClass = (new Page())->getMorphClass();
|
||||
|
||||
$existsQuery = function ($permissionQuery) use ($fullPageIdColumn, $morphClass) {
|
||||
/** @var Builder $permissionQuery */
|
||||
$permissionQuery->select('joint_permissions.role_id')->from('joint_permissions')
|
||||
->whereColumn('joint_permissions.entity_id', '=', $fullPageIdColumn)
|
||||
->where('joint_permissions.entity_type', '=', $morphClass)
|
||||
->whereIn('joint_permissions.role_id', $this->getCurrentUserRoleIds())
|
||||
->where(function (QueryBuilder $query) {
|
||||
$this->addJointHasPermissionCheck($query, $this->currentUser()->id);
|
||||
});
|
||||
};
|
||||
|
||||
$q = $query->where(function ($query) use ($existsQuery, $fullPageIdColumn) {
|
||||
$query->whereExists($existsQuery)
|
||||
->orWhere($fullPageIdColumn, '=', 0);
|
||||
});
|
||||
|
||||
// Prevent visibility of non-owned draft pages
|
||||
$q->whereExists(function (QueryBuilder $query) use ($fullPageIdColumn) {
|
||||
$query->select('id')->from('pages')
|
||||
->whereColumn('pages.id', '=', $fullPageIdColumn)
|
||||
->where(function (QueryBuilder $query) {
|
||||
$query->where('pages.draft', '=', false)
|
||||
->orWhere('pages.owned_by', '=', $this->currentUser()->id);
|
||||
});
|
||||
});
|
||||
|
||||
return $q;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the query for checking the given user id has permission
|
||||
* within the join_permissions table.
|
||||
*
|
||||
* @param QueryBuilder|Builder $query
|
||||
*/
|
||||
protected function addJointHasPermissionCheck($query, int $userIdToCheck)
|
||||
{
|
||||
$query->where('joint_permissions.has_permission', '=', true)->orWhere(function ($query) use ($userIdToCheck) {
|
||||
$query->where('joint_permissions.has_permission_own', '=', true)
|
||||
->where('joint_permissions.owned_by', '=', $userIdToCheck);
|
||||
});
|
||||
$this->applyPermissionsToQuery($query, $tableName, $morphClass, $pageIdColumn, '');
|
||||
// TODO - Draft display
|
||||
// TODO - Likely need owned_by entity join workaround as used above
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -21,19 +21,32 @@ class PermissionFormData
|
||||
{
|
||||
return $this->entity->permissions()
|
||||
->with('role')
|
||||
->where('role_id', '!=', 0)
|
||||
->whereNotNull('role_id')
|
||||
->get()
|
||||
->sortBy('role.display_name')
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the permissions with assigned users.
|
||||
*/
|
||||
public function permissionsWithUsers(): array
|
||||
{
|
||||
return $this->entity->permissions()
|
||||
->with('user')
|
||||
->whereNotNull('user_id')
|
||||
->get()
|
||||
->sortBy('user.name')
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the roles that don't yet have specific permissions for the
|
||||
* entity we're managing permissions for.
|
||||
*/
|
||||
public function rolesNotAssigned(): array
|
||||
{
|
||||
$assigned = $this->entity->permissions()->pluck('role_id');
|
||||
$assigned = $this->entity->permissions()->whereNotNull('role_id')->pluck('role_id');
|
||||
return Role::query()
|
||||
->where('system_name', '!=', 'admin')
|
||||
->whereNotIn('id', $assigned)
|
||||
@@ -49,20 +62,19 @@ class PermissionFormData
|
||||
{
|
||||
/** @var ?EntityPermission $permission */
|
||||
$permission = $this->entity->permissions()
|
||||
->where('role_id', '=', 0)
|
||||
->whereNull(['role_id', 'user_id'])
|
||||
->first();
|
||||
return $permission ?? (new EntityPermission());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "Everyone Else" role entry.
|
||||
* Check if the "Everyone else" option is inheriting default role system permissions.
|
||||
* Is determined by any system entity_permission existing for the current entity.
|
||||
*/
|
||||
public function everyoneElseRole(): Role
|
||||
public function everyoneElseInheriting(): bool
|
||||
{
|
||||
return (new Role())->forceFill([
|
||||
'id' => 0,
|
||||
'display_name' => trans('entities.permissions_role_everyone_else'),
|
||||
'description' => trans('entities.permissions_role_everyone_else_desc'),
|
||||
]);
|
||||
return !$this->entity->permissions()
|
||||
->whereNull(['role_id', 'user_id'])
|
||||
->exists();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,13 +11,13 @@ use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class PermissionsRepo
|
||||
{
|
||||
protected JointPermissionBuilder $permissionBuilder;
|
||||
protected $systemRoles = ['admin', 'public'];
|
||||
protected CollapsedPermissionBuilder $permissionBuilder;
|
||||
protected array $systemRoles = ['admin', 'public'];
|
||||
|
||||
/**
|
||||
* PermissionsRepo constructor.
|
||||
*/
|
||||
public function __construct(JointPermissionBuilder $permissionBuilder)
|
||||
public function __construct(CollapsedPermissionBuilder $permissionBuilder)
|
||||
{
|
||||
$this->permissionBuilder = $permissionBuilder;
|
||||
}
|
||||
@@ -57,7 +57,6 @@ class PermissionsRepo
|
||||
|
||||
$permissions = isset($roleData['permissions']) ? array_keys($roleData['permissions']) : [];
|
||||
$this->assignRolePermissions($role, $permissions);
|
||||
$this->permissionBuilder->rebuildForRole($role);
|
||||
|
||||
Activity::add(ActivityType::ROLE_CREATE, $role);
|
||||
|
||||
@@ -88,7 +87,6 @@ class PermissionsRepo
|
||||
$role->fill($roleData);
|
||||
$role->mfa_enforced = ($roleData['mfa_enforced'] ?? 'false') === 'true';
|
||||
$role->save();
|
||||
$this->permissionBuilder->rebuildForRole($role);
|
||||
|
||||
Activity::add(ActivityType::ROLE_UPDATE, $role);
|
||||
}
|
||||
@@ -140,7 +138,7 @@ class PermissionsRepo
|
||||
}
|
||||
|
||||
$role->entityPermissions()->delete();
|
||||
$role->jointPermissions()->delete();
|
||||
$role->collapsedPermissions()->delete();
|
||||
Activity::add(ActivityType::ROLE_DELETE, $role);
|
||||
$role->delete();
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ class SimpleEntityData
|
||||
{
|
||||
public int $id;
|
||||
public string $type;
|
||||
public int $owned_by;
|
||||
public ?int $book_id;
|
||||
public ?int $chapter_id;
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace BookStack\Auth;
|
||||
|
||||
use BookStack\Auth\Permissions\CollapsedPermission;
|
||||
use BookStack\Auth\Permissions\EntityPermission;
|
||||
use BookStack\Auth\Permissions\JointPermission;
|
||||
use BookStack\Auth\Permissions\RolePermission;
|
||||
use BookStack\Interfaces\Loggable;
|
||||
use BookStack\Model;
|
||||
@@ -39,14 +39,6 @@ class Role extends Model implements Loggable
|
||||
return $this->belongsToMany(User::class)->orderBy('name', 'asc');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all related JointPermissions.
|
||||
*/
|
||||
public function jointPermissions(): HasMany
|
||||
{
|
||||
return $this->hasMany(JointPermission::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* The RolePermissions that belong to the role.
|
||||
*/
|
||||
@@ -63,6 +55,14 @@ class Role extends Model implements Loggable
|
||||
return $this->hasMany(EntityPermission::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all related entity collapsed permissions.
|
||||
*/
|
||||
public function collapsedPermissions(): HasMany
|
||||
{
|
||||
return $this->hasMany(CollapsedPermission::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this role has a permission.
|
||||
*/
|
||||
|
||||
@@ -5,6 +5,8 @@ namespace BookStack\Auth;
|
||||
use BookStack\Actions\Favourite;
|
||||
use BookStack\Api\ApiToken;
|
||||
use BookStack\Auth\Access\Mfa\MfaValue;
|
||||
use BookStack\Auth\Permissions\CollapsedPermission;
|
||||
use BookStack\Auth\Permissions\EntityPermission;
|
||||
use BookStack\Entities\Tools\SlugGenerator;
|
||||
use BookStack\Interfaces\Loggable;
|
||||
use BookStack\Interfaces\Sluggable;
|
||||
@@ -298,6 +300,22 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
||||
}, 'activities', 'users.id', '=', 'activities.user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entity permissions assigned to this specific user.
|
||||
*/
|
||||
public function entityPermissions(): HasMany
|
||||
{
|
||||
return $this->hasMany(EntityPermission::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all related entity collapsed permissions.
|
||||
*/
|
||||
public function collapsedPermissions(): HasMany
|
||||
{
|
||||
return $this->hasMany(CollapsedPermission::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the url for editing this user.
|
||||
*/
|
||||
|
||||
@@ -153,6 +153,8 @@ class UserRepo
|
||||
$user->apiTokens()->delete();
|
||||
$user->favourites()->delete();
|
||||
$user->mfaValues()->delete();
|
||||
$user->collapsedPermissions()->delete();
|
||||
$user->entityPermissions()->delete();
|
||||
$user->delete();
|
||||
|
||||
// Delete user profile images
|
||||
@@ -234,8 +236,6 @@ class UserRepo
|
||||
*/
|
||||
protected function setUserRoles(User $user, array $roles)
|
||||
{
|
||||
$roles = array_filter(array_values($roles));
|
||||
|
||||
if ($this->demotingLastAdmin($user, $roles)) {
|
||||
throw new UserUpdateException(trans('errors.role_cannot_remove_only_admin'), $user->getEditUrl());
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace BookStack\Console\Commands;
|
||||
|
||||
use BookStack\Auth\Permissions\JointPermissionBuilder;
|
||||
use BookStack\Auth\Permissions\CollapsedPermissionBuilder;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
@@ -22,12 +22,12 @@ class RegeneratePermissions extends Command
|
||||
*/
|
||||
protected $description = 'Regenerate all system permissions';
|
||||
|
||||
protected JointPermissionBuilder $permissionBuilder;
|
||||
protected CollapsedPermissionBuilder $permissionBuilder;
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*/
|
||||
public function __construct(JointPermissionBuilder $permissionBuilder)
|
||||
public function __construct(CollapsedPermissionBuilder $permissionBuilder)
|
||||
{
|
||||
$this->permissionBuilder = $permissionBuilder;
|
||||
parent::__construct();
|
||||
|
||||
@@ -7,9 +7,9 @@ use BookStack\Actions\Comment;
|
||||
use BookStack\Actions\Favourite;
|
||||
use BookStack\Actions\Tag;
|
||||
use BookStack\Actions\View;
|
||||
use BookStack\Auth\Permissions\CollapsedPermission;
|
||||
use BookStack\Auth\Permissions\EntityPermission;
|
||||
use BookStack\Auth\Permissions\JointPermission;
|
||||
use BookStack\Auth\Permissions\JointPermissionBuilder;
|
||||
use BookStack\Auth\Permissions\CollapsedPermissionBuilder;
|
||||
use BookStack\Auth\Permissions\PermissionApplicator;
|
||||
use BookStack\Entities\Tools\SlugGenerator;
|
||||
use BookStack\Interfaces\Deletable;
|
||||
@@ -69,7 +69,7 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
|
||||
*/
|
||||
public function scopeVisible(Builder $query): Builder
|
||||
{
|
||||
return app()->make(PermissionApplicator::class)->restrictEntityQuery($query);
|
||||
return app()->make(PermissionApplicator::class)->restrictEntityQuery($query, $this->getMorphClass());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -187,11 +187,11 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entity jointPermissions this is connected to.
|
||||
* Get the entity collapsed permissions this is connected to.
|
||||
*/
|
||||
public function jointPermissions(): MorphMany
|
||||
public function collapsedPermissions(): MorphMany
|
||||
{
|
||||
return $this->morphMany(JointPermission::class, 'entity');
|
||||
return $this->morphMany(CollapsedPermission::class, 'entity');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -292,7 +292,7 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
|
||||
*/
|
||||
public function rebuildPermissions()
|
||||
{
|
||||
app()->make(JointPermissionBuilder::class)->rebuildForEntity(clone $this);
|
||||
app()->make(CollapsedPermissionBuilder::class)->rebuildForEntity(clone $this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,7 +10,6 @@ use BookStack\Entities\Models\Bookshelf;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Facades\Activity;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class PermissionsUpdater
|
||||
{
|
||||
@@ -58,13 +57,30 @@ class PermissionsUpdater
|
||||
protected function formatPermissionsFromRequestToEntityPermissions(array $permissions): array
|
||||
{
|
||||
$formatted = [];
|
||||
$columnsByType = [
|
||||
'role' => 'role_id',
|
||||
'user' => 'user_id',
|
||||
'fallback' => '',
|
||||
];
|
||||
|
||||
foreach ($permissions as $roleId => $info) {
|
||||
$entityPermissionData = ['role_id' => $roleId];
|
||||
foreach (EntityPermission::PERMISSIONS as $permission) {
|
||||
$entityPermissionData[$permission] = (($info[$permission] ?? false) === "true");
|
||||
foreach ($permissions as $type => $byId) {
|
||||
$column = $columnsByType[$type] ?? null;
|
||||
if (is_null($column)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($byId as $id => $info) {
|
||||
$entityPermissionData = [];
|
||||
|
||||
if (!empty($column)) {
|
||||
$entityPermissionData[$column] = $id;
|
||||
}
|
||||
|
||||
foreach (EntityPermission::PERMISSIONS as $permission) {
|
||||
$entityPermissionData[$permission] = (($info[$permission] ?? false) === "true");
|
||||
}
|
||||
$formatted[] = $entityPermissionData;
|
||||
}
|
||||
$formatted[] = $entityPermissionData;
|
||||
}
|
||||
|
||||
return $formatted;
|
||||
|
||||
@@ -372,7 +372,7 @@ class TrashCan
|
||||
$entity->permissions()->delete();
|
||||
$entity->tags()->delete();
|
||||
$entity->comments()->delete();
|
||||
$entity->jointPermissions()->delete();
|
||||
$entity->collapsedPermissions()->delete();
|
||||
$entity->searchTerms()->delete();
|
||||
$entity->deletions()->delete();
|
||||
$entity->favourites()->delete();
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace BookStack\Http\Controllers;
|
||||
use BookStack\Auth\Permissions\EntityPermission;
|
||||
use BookStack\Auth\Permissions\PermissionFormData;
|
||||
use BookStack\Auth\Role;
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Models\Bookshelf;
|
||||
use BookStack\Entities\Models\Chapter;
|
||||
@@ -162,10 +163,35 @@ class PermissionsController extends Controller
|
||||
{
|
||||
$this->checkPermissionOr('restrictions-manage-all', fn() => userCan('restrictions-manage-own'));
|
||||
|
||||
/** @var Role $role */
|
||||
$role = Role::query()->findOrFail($roleId);
|
||||
|
||||
return view('form.entity-permissions-row', [
|
||||
'role' => $role,
|
||||
'modelType' => 'role',
|
||||
'modelId' => $role->id,
|
||||
'modelName' => $role->display_name,
|
||||
'modelDescription' => $role->description,
|
||||
'permission' => new EntityPermission(),
|
||||
'entityType' => $entityType,
|
||||
'inheriting' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an empty entity permissions form row for the given user.
|
||||
*/
|
||||
public function formRowForUser(string $entityType, string $userId)
|
||||
{
|
||||
$this->checkPermissionOr('restrictions-manage-all', fn() => userCan('restrictions-manage-own'));
|
||||
|
||||
/** @var User $user */
|
||||
$user = User::query()->findOrFail($userId);
|
||||
|
||||
return view('form.entity-permissions-row', [
|
||||
'modelType' => 'user',
|
||||
'modelId' => $user->id,
|
||||
'modelName' => $user->name,
|
||||
'modelDescription' => '',
|
||||
'permission' => new EntityPermission(),
|
||||
'entityType' => $entityType,
|
||||
'inheriting' => false,
|
||||
|
||||
@@ -223,7 +223,7 @@ class SearchRunner
|
||||
});
|
||||
$subQuery->groupBy('entity_type', 'entity_id');
|
||||
|
||||
$entityQuery->joinSub($subQuery, 's', 'id', '=', 'entity_id');
|
||||
$entityQuery->joinSub($subQuery, 's', 'id', '=', 's.entity_id');
|
||||
$entityQuery->addSelect('s.score');
|
||||
$entityQuery->orderBy('score', 'desc');
|
||||
}
|
||||
|
||||
@@ -55,8 +55,9 @@ function hasAppAccess(): bool
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current user has a permission. If an ownable element
|
||||
* is passed in the jointPermissions are checked against that particular item.
|
||||
* Check if the current user has a permission.
|
||||
* Checks a generic role permission or, if an ownable model is passed in, it will
|
||||
* check against the given entity model, taking into account entity-level permissions.
|
||||
*/
|
||||
function userCan(string $permission, Model $ownable = null): bool
|
||||
{
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
project_identifier: bookstack
|
||||
base_path: .
|
||||
preserve_hierarchy: false
|
||||
pull_request_title: Updated translations with latest Crowdin changes
|
||||
pull_request_labels:
|
||||
- ":earth_africa: Translations"
|
||||
files:
|
||||
- source: /resources/lang/en/*.php
|
||||
translation: /resources/lang/%two_letters_code%/%original_file_name%
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddUserIdToEntityPermissions extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('entity_permissions', function (Blueprint $table) {
|
||||
$table->unsignedInteger('role_id')->nullable()->default(null)->change();
|
||||
$table->unsignedInteger('user_id')->nullable()->default(null)->index();
|
||||
});
|
||||
|
||||
DB::table('entity_permissions')
|
||||
->where('role_id', '=', 0)
|
||||
->update(['role_id' => null]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
DB::table('entity_permissions')
|
||||
->whereNull('role_id')
|
||||
->update(['role_id' => 0]);
|
||||
|
||||
DB::table('entity_permissions')
|
||||
->whereNotNull('user_id')
|
||||
->delete();
|
||||
|
||||
Schema::table('entity_permissions', function (Blueprint $table) {
|
||||
$table->unsignedInteger('role_id')->nullable(false)->change();
|
||||
$table->dropColumn('user_id');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateCollapsedRolePermissionsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
// TODO - Drop joint permissions
|
||||
// TODO - Run collapsed table rebuild.
|
||||
|
||||
Schema::create('entity_permissions_collapsed', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedInteger('role_id')->nullable()->index();
|
||||
$table->unsignedInteger('user_id')->nullable()->index();
|
||||
$table->string('entity_type');
|
||||
$table->unsignedInteger('entity_id');
|
||||
$table->boolean('view')->index();
|
||||
|
||||
$table->index(['entity_type', 'entity_id']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('entity_permissions_collapsed');
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace Database\Seeders;
|
||||
|
||||
use BookStack\Api\ApiToken;
|
||||
use BookStack\Auth\Permissions\JointPermissionBuilder;
|
||||
use BookStack\Auth\Permissions\CollapsedPermissionBuilder;
|
||||
use BookStack\Auth\Permissions\RolePermission;
|
||||
use BookStack\Auth\Role;
|
||||
use BookStack\Auth\User;
|
||||
@@ -69,7 +69,7 @@ class DummyContentSeeder extends Seeder
|
||||
]);
|
||||
$token->save();
|
||||
|
||||
app(JointPermissionBuilder::class)->rebuildForAll();
|
||||
app(CollapsedPermissionBuilder::class)->rebuildForAll();
|
||||
app(SearchIndex::class)->indexAllEntities();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use BookStack\Auth\Permissions\JointPermissionBuilder;
|
||||
use BookStack\Auth\Permissions\CollapsedPermissionBuilder;
|
||||
use BookStack\Auth\Role;
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Entities\Models\Book;
|
||||
@@ -35,7 +35,7 @@ class LargeContentSeeder extends Seeder
|
||||
$largeBook->chapters()->saveMany($chapters);
|
||||
$all = array_merge([$largeBook], array_values($pages->all()), array_values($chapters->all()));
|
||||
|
||||
app()->make(JointPermissionBuilder::class)->rebuildForEntity($largeBook);
|
||||
app()->make(CollapsedPermissionBuilder::class)->rebuildForEntity($largeBook);
|
||||
app()->make(SearchIndex::class)->indexEntities($all);
|
||||
}
|
||||
}
|
||||
|
||||
421
dev/docs/permission-scenario-testing.md
Normal file
421
dev/docs/permission-scenario-testing.md
Normal file
@@ -0,0 +1,421 @@
|
||||
# Permission Scenario Testing
|
||||
|
||||
Due to complexity that can arise in the various combinations of permissions, this document details scenarios and their expected results.
|
||||
|
||||
Test cases are written ability abstract, since all abilities should act the same in theory. Functional test cases may test abilities separate due to implementation differences.
|
||||
|
||||
Tests are categorised by the most specific element involved in the scenario, where the below list is most specific to least:
|
||||
|
||||
- User entity permissions.
|
||||
- Role entity permissions.
|
||||
- Fallback entity permissions.
|
||||
- Role permissions.
|
||||
|
||||
- TODO - Test fallback in the context of the above.
|
||||
|
||||
## General Permission Logical Rules
|
||||
|
||||
The below are some general rules we follow to standardise the behaviour of permissions in the platform:
|
||||
|
||||
- Most specific permission application (as above) take priority and can deny less specific permissions.
|
||||
- Parent user/role entity permissions that may be inherited, are considered to essentially be applied on the item they are inherited to unless a lower level has its own permission rule for an already specific role/user.
|
||||
- Where both grant and deny exist at the same specificity, we side towards grant.
|
||||
|
||||
## Cases
|
||||
|
||||
### Content Role Permissions
|
||||
|
||||
These are tests related to item/entity permissions that are set only at a role level.
|
||||
|
||||
#### test_01_allow
|
||||
|
||||
- Role A has role all-page permission.
|
||||
- User has Role A.
|
||||
|
||||
User granted page permission.
|
||||
|
||||
#### test_02_deny
|
||||
|
||||
- Role A has no page permission.
|
||||
- User has Role A.
|
||||
|
||||
User denied page permission.
|
||||
|
||||
#### test_10_allow_on_own_with_own
|
||||
|
||||
- Role A has role own-page permission.
|
||||
- User has Role A.
|
||||
- User is owner of page.
|
||||
|
||||
User granted page permission.
|
||||
|
||||
#### test_11_deny_on_other_with_own
|
||||
|
||||
- Role A has role own-page permission.
|
||||
- User has Role A.
|
||||
- User is not owner of page.
|
||||
|
||||
User denied page permission.
|
||||
|
||||
#### test_20_multiple_role_conflicting_all
|
||||
|
||||
- Role A has role all-page permission.
|
||||
- Role B has no page permission.
|
||||
- User has Role A & B.
|
||||
|
||||
User granted page permission.
|
||||
|
||||
#### test_21_multiple_role_conflicting_own
|
||||
|
||||
- Role A has role own-page permission.
|
||||
- Role B has no page permission.
|
||||
- User has Role A & B.
|
||||
- User is owner of page.
|
||||
|
||||
User granted page permission.
|
||||
|
||||
---
|
||||
|
||||
### Entity Role Permissions
|
||||
|
||||
These are tests related to entity-level role-specific permission overrides.
|
||||
|
||||
#### test_01_explicit_allow
|
||||
|
||||
- Page permissions have inherit disabled.
|
||||
- Role A has entity allow page permission.
|
||||
- User has Role A.
|
||||
|
||||
User granted page permission.
|
||||
|
||||
#### test_02_explicit_deny
|
||||
|
||||
- Page permissions have inherit disabled.
|
||||
- Role A has entity deny page permission.
|
||||
- User has Role A.
|
||||
|
||||
User denied page permission.
|
||||
|
||||
#### test_03_same_level_conflicting
|
||||
|
||||
- Page permissions have inherit disabled.
|
||||
- Role A has entity allow page permission.
|
||||
- Role B has entity deny page permission.
|
||||
- User has both Role A & B.
|
||||
|
||||
User granted page permission.
|
||||
Explicit grant overrides entity deny at same level.
|
||||
|
||||
#### test_20_inherit_allow
|
||||
|
||||
- Page permissions have inherit enabled.
|
||||
- Chapter permissions has inherit disabled.
|
||||
- Role A has entity allow chapter permission.
|
||||
- User has Role A.
|
||||
|
||||
User granted page permission.
|
||||
|
||||
#### test_21_inherit_deny
|
||||
|
||||
- Page permissions have inherit enabled.
|
||||
- Chapter permissions has inherit disabled.
|
||||
- Role A has entity deny chapter permission.
|
||||
- User has Role A.
|
||||
|
||||
User denied page permission.
|
||||
|
||||
#### test_22_same_level_conflict_inherit
|
||||
|
||||
- Page permissions have inherit enabled.
|
||||
- Chapter permissions has inherit disabled.
|
||||
- Role A has entity deny chapter permission.
|
||||
- Role B has entity allow chapter permission.
|
||||
- User has both Role A & B.
|
||||
|
||||
User granted page permission.
|
||||
|
||||
#### test_30_child_inherit_override_allow
|
||||
|
||||
- Page permissions have inherit enabled.
|
||||
- Chapter permissions has inherit disabled.
|
||||
- Role A has entity deny chapter permission.
|
||||
- Role A has entity allow page permission.
|
||||
- User has Role A.
|
||||
|
||||
User granted page permission.
|
||||
|
||||
#### test_31_child_inherit_override_deny
|
||||
|
||||
- Page permissions have inherit enabled.
|
||||
- Chapter permissions has inherit disabled.
|
||||
- Role A has entity allow chapter permission.
|
||||
- Role A has entity deny page permission.
|
||||
- User has Role A.
|
||||
|
||||
User denied page permission.
|
||||
|
||||
#### test_40_multi_role_inherit_conflict_override_deny
|
||||
|
||||
- Page permissions have inherit enabled.
|
||||
- Chapter permissions has inherit disabled.
|
||||
- Role A has entity deny page permission.
|
||||
- Role B has entity allow chapter permission.
|
||||
- User has Role A & B.
|
||||
|
||||
User granted page permission.
|
||||
|
||||
#### test_41_multi_role_inherit_conflict_retain_allow
|
||||
|
||||
- Page permissions have inherit enabled.
|
||||
- Chapter permissions has inherit disabled.
|
||||
- Role A has entity allow page permission.
|
||||
- Role B has entity deny chapter permission.
|
||||
- User has Role A & B.
|
||||
|
||||
User granted page permission.
|
||||
|
||||
#### test_50_role_override_allow
|
||||
|
||||
- Page permissions have inherit enabled.
|
||||
- Role A has no page role permission.
|
||||
- Role A has entity allow page permission.
|
||||
- User has Role A.
|
||||
|
||||
User granted page permission.
|
||||
|
||||
#### test_51_role_override_deny
|
||||
|
||||
- Page permissions have inherit enabled.
|
||||
- Role A has no page-view-all role permission.
|
||||
- Role A has entity deny page permission.
|
||||
- User has Role A.
|
||||
|
||||
User denied page permission.
|
||||
|
||||
#### test_60_inherited_role_override_allow
|
||||
|
||||
- Page permissions have inherit enabled.
|
||||
- Chapter permissions have inherit enabled.
|
||||
- Role A has no page role permission.
|
||||
- Role A has entity allow chapter permission.
|
||||
- User has Role A.
|
||||
|
||||
User granted page permission.
|
||||
|
||||
#### test_61_inherited_role_override_deny
|
||||
|
||||
- Page permissions have inherit enabled.
|
||||
- Chapter permissions have inherit enabled.
|
||||
- Role A has page role permission.
|
||||
- Role A has entity denied chapter permission.
|
||||
- User has Role A.
|
||||
|
||||
User denied page permission.
|
||||
|
||||
#### test_62_inherited_role_override_deny_on_own
|
||||
|
||||
- Page permissions have inherit enabled.
|
||||
- Chapter permissions have inherit enabled.
|
||||
- Role A has own-page role permission.
|
||||
- Role A has entity denied chapter permission.
|
||||
- User has Role A.
|
||||
- User owns Page.
|
||||
|
||||
User denied page permission.
|
||||
|
||||
#### test_70_multi_role_inheriting_deny
|
||||
|
||||
- Page permissions have inherit enabled.
|
||||
- Role A has all page role permission.
|
||||
- Role B has entity denied page permission.
|
||||
- User has Role A and B.
|
||||
|
||||
User denied page permission.
|
||||
|
||||
#### test_80_multi_role_inherited_deny_via_parent
|
||||
|
||||
- Page permissions have inherit enabled.
|
||||
- Chapter permissions have inherit enabled.
|
||||
- Role A has all-pages role permission.
|
||||
- Role B has entity denied chapter permission.
|
||||
- User has Role A & B.
|
||||
|
||||
User denied page permission.
|
||||
|
||||
---
|
||||
|
||||
### Entity User Permissions
|
||||
|
||||
These are tests related to entity-level user-specific permission overrides.
|
||||
|
||||
#### test_01_explicit_allow
|
||||
|
||||
- Page permissions have inherit disabled.
|
||||
- User has entity allow page permission.
|
||||
|
||||
User granted page permission.
|
||||
|
||||
#### test_02_explicit_deny
|
||||
|
||||
- Page permissions have inherit disabled.
|
||||
- User has entity deny page permission.
|
||||
|
||||
User denied page permission.
|
||||
|
||||
#### test_10_allow_inherit
|
||||
|
||||
- Page permissions have inherit enabled.
|
||||
- Chapter permissions have inherit disabled.
|
||||
- User has entity allow chapter permission.
|
||||
|
||||
User granted page permission.
|
||||
|
||||
#### test_11_deny_inherit
|
||||
|
||||
- Page permissions have inherit enabled.
|
||||
- Chapter permissions have inherit disabled.
|
||||
- User has entity deny chapter permission.
|
||||
|
||||
User denied page permission.
|
||||
|
||||
#### test_12_allow_inherit_override
|
||||
|
||||
- Page permissions have inherit enabled.
|
||||
- Chapter permissions have inherit disabled.
|
||||
- User has entity deny chapter permission.
|
||||
- User has entity allow page permission.
|
||||
|
||||
User granted page permission.
|
||||
|
||||
#### test_13_deny_inherit_override
|
||||
|
||||
- Page permissions have inherit enabled.
|
||||
- Chapter permissions have inherit disabled.
|
||||
- User has entity allow chapter permission.
|
||||
- User has entity deny page permission.
|
||||
|
||||
User denied page permission.
|
||||
|
||||
#### test_40_entity_role_override_allow
|
||||
|
||||
- Page permissions have inherit disabled.
|
||||
- User has entity allow page permission.
|
||||
- Role A has entity deny page permission.
|
||||
- User has role A.
|
||||
|
||||
User granted page permission.
|
||||
|
||||
#### test_41_entity_role_override_deny
|
||||
|
||||
- Page permissions have inherit disabled.
|
||||
- User has entity deny page permission.
|
||||
- Role A has entity allow page permission.
|
||||
- User has role A.
|
||||
|
||||
User denied page permission.
|
||||
|
||||
#### test_42_entity_role_override_allow_via_inherit
|
||||
|
||||
- Page permissions have inherit enabled.
|
||||
- Chapter permissions have inherit disabled.
|
||||
- User has entity allow chapter permission.
|
||||
- Role A has entity deny page permission.
|
||||
- User has role A.
|
||||
|
||||
User granted page permission.
|
||||
|
||||
#### test_43_entity_role_override_deny_via_inherit
|
||||
|
||||
- Page permissions have inherit enabled.
|
||||
- Chapter permissions have inherit disabled.
|
||||
- User has entity deny chapter permission.
|
||||
- Role A has entity allow page permission.
|
||||
- User has role A.
|
||||
|
||||
User denied page permission.
|
||||
|
||||
#### test_50_role_override_allow
|
||||
|
||||
- Page permissions have inherit enabled.
|
||||
- Role A has no page role permission.
|
||||
- User has entity allow page permission.
|
||||
- User has Role A.
|
||||
|
||||
User granted page permission.
|
||||
|
||||
#### test_51_role_override_deny
|
||||
|
||||
- Page permissions have inherit enabled.
|
||||
- Role A has all-page role permission.
|
||||
- User has entity deny page permission.
|
||||
- User has Role A.
|
||||
|
||||
User denied page permission.
|
||||
|
||||
#### test_60_inherited_role_override_allow
|
||||
|
||||
- Page permissions have inherit enabled.
|
||||
- Role A has no page role permission.
|
||||
- User has entity allow chapter permission.
|
||||
- User has Role A.
|
||||
|
||||
User granted page permission.
|
||||
|
||||
#### test_61_inherited_role_override_deny
|
||||
|
||||
- Page permissions have inherit enabled.
|
||||
- Role A has view-all page role permission.
|
||||
- User has entity deny chapter permission.
|
||||
- User has Role A.
|
||||
|
||||
User denied page permission.
|
||||
|
||||
#### test_61_inherited_role_override_deny_on_own
|
||||
|
||||
- Page permissions have inherit enabled.
|
||||
- Role A has view-own page role permission.
|
||||
- User has entity deny chapter permission.
|
||||
- User has Role A.
|
||||
- User owns Page.
|
||||
|
||||
User denied page permission.
|
||||
|
||||
#### test_70_all_override_allow
|
||||
|
||||
- Page permissions have inherit enabled.
|
||||
- Role A has no page role permission.
|
||||
- Role A has entity deny page permission.
|
||||
- User has entity allow page permission.
|
||||
- User has Role A.
|
||||
|
||||
User granted page permission.
|
||||
|
||||
#### test_71_all_override_deny
|
||||
|
||||
- Page permissions have inherit enabled.
|
||||
- Role A has page-all role permission.
|
||||
- Role A has entity allow page permission.
|
||||
- User has entity deny page permission.
|
||||
- User has Role A.
|
||||
|
||||
User denied page permission.
|
||||
|
||||
#### test_80_inherited_all_override_allow
|
||||
|
||||
- Page permissions have inherit enabled.
|
||||
- Role A has no page role permission.
|
||||
- Role A has entity deny chapter permission.
|
||||
- User has entity allow chapter permission.
|
||||
- User has Role A.
|
||||
|
||||
User granted page permission.
|
||||
|
||||
#### test_81_inherited_all_override_deny
|
||||
|
||||
- Page permissions have inherit enabled.
|
||||
- Role A has view-all page role permission.
|
||||
- Role A has entity allow chapter permission.
|
||||
- User has entity deny chapter permission.
|
||||
- User has Role A.
|
||||
|
||||
User denied page permission.
|
||||
62
public/dist/app.js
vendored
62
public/dist/app.js
vendored
File diff suppressed because one or more lines are too long
34
public/dist/code.js
vendored
34
public/dist/code.js
vendored
File diff suppressed because one or more lines are too long
1
public/dist/export-styles.css
vendored
1
public/dist/export-styles.css
vendored
File diff suppressed because one or more lines are too long
1
public/dist/print-styles.css
vendored
1
public/dist/print-styles.css
vendored
@@ -1 +0,0 @@
|
||||
:root{--color-primary: #206ea7;--color-primary-light: rgba(32,110,167,0.15);--color-page: #206ea7;--color-page-draft: #7e50b1;--color-chapter: #af4d0d;--color-book: #077b70;--color-bookshelf: #a94747;--bg-disabled: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='100%25' width='100%25'%3E%3Cdefs%3E%3Cpattern id='doodad' width='19' height='19' viewBox='0 0 40 40' patternUnits='userSpaceOnUse' patternTransform='rotate(143)'%3E%3Crect width='100%25' height='100%25' fill='rgba(42, 67, 101,0)'/%3E%3Cpath d='M-10 30h60v20h-60zM-10-10h60v20h-60' fill='rgba(26, 32, 44,0)'/%3E%3Cpath d='M-10 10h60v20h-60zM-10-30h60v20h-60z' fill='rgba(0, 0, 0,0.05)'/%3E%3C/pattern%3E%3C/defs%3E%3Crect fill='url(%23doodad)' height='200%25' width='200%25'/%3E%3C/svg%3E")}:root.dark-mode{--bg-disabled: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='100%25' width='100%25'%3E%3Cdefs%3E%3Cpattern id='doodad' width='19' height='19' viewBox='0 0 40 40' patternUnits='userSpaceOnUse' patternTransform='rotate(143)'%3E%3Crect width='100%25' height='100%25' fill='rgba(42, 67, 101,0)'/%3E%3Cpath d='M-10 30h60v20h-60zM-10-10h60v20h-60' fill='rgba(26, 32, 44,0)'/%3E%3Cpath d='M-10 10h60v20h-60zM-10-30h60v20h-60z' fill='rgba(255, 255, 255,0.05)'/%3E%3C/pattern%3E%3C/defs%3E%3Crect fill='url(%23doodad)' height='200%25' width='200%25'/%3E%3C/svg%3E");color-scheme:only dark}:root:not(.dark-mode){color-scheme:only light}header{display:none}html,body{font-size:12px;background-color:#fff}.page-content{margin:0 auto}.print-hidden{display:none !important}.tri-layout-container{grid-template-columns:1fr;grid-template-areas:"b";margin-inline-start:0;margin-inline-end:0;display:block}.card{box-shadow:none}.content-wrap.card{padding-inline-start:0;padding-inline-end:0}/*# sourceMappingURL=print-styles.css.map */
|
||||
1
public/dist/styles.css
vendored
1
public/dist/styles.css
vendored
File diff suppressed because one or more lines are too long
@@ -26,12 +26,10 @@ import 'codemirror/mode/python/python';
|
||||
import 'codemirror/mode/ruby/ruby';
|
||||
import 'codemirror/mode/rust/rust';
|
||||
import 'codemirror/mode/shell/shell';
|
||||
import 'codemirror/mode/smarty/smarty';
|
||||
import 'codemirror/mode/sql/sql';
|
||||
import 'codemirror/mode/stex/stex';
|
||||
import 'codemirror/mode/swift/swift';
|
||||
import 'codemirror/mode/toml/toml';
|
||||
import 'codemirror/mode/twig/twig';
|
||||
import 'codemirror/mode/vb/vb';
|
||||
import 'codemirror/mode/vbscript/vbscript';
|
||||
import 'codemirror/mode/xml/xml';
|
||||
@@ -96,13 +94,11 @@ const modeMap = {
|
||||
rs: 'rust',
|
||||
shell: 'shell',
|
||||
sh: 'shell',
|
||||
smarty: 'smarty',
|
||||
sql: 'text/x-sql',
|
||||
stext: 'text/x-stex',
|
||||
swift: 'text/x-swift',
|
||||
toml: 'toml',
|
||||
ts: 'text/typescript',
|
||||
twig: 'twig',
|
||||
typescript: 'text/typescript',
|
||||
vbs: 'vbscript',
|
||||
vbscript: 'vbscript',
|
||||
|
||||
@@ -10,6 +10,8 @@ export class EntityPermissions extends Component {
|
||||
this.everyoneInheritToggle = this.$refs.everyoneInherit;
|
||||
this.roleSelect = this.$refs.roleSelect;
|
||||
this.roleContainer = this.$refs.roleContainer;
|
||||
this.userContainer = this.$refs.userContainer;
|
||||
this.userSelectContainer = this.$refs.userSelectContainer;
|
||||
|
||||
this.setupListeners();
|
||||
}
|
||||
@@ -18,7 +20,7 @@ export class EntityPermissions extends Component {
|
||||
// "Everyone Else" inherit toggle
|
||||
this.everyoneInheritToggle.addEventListener('change', event => {
|
||||
const inherit = event.target.checked;
|
||||
const permissions = document.querySelectorAll('input[name^="permissions[0]["]');
|
||||
const permissions = document.querySelectorAll('input[name^="permissions[fallback]"]');
|
||||
for (const permission of permissions) {
|
||||
permission.disabled = inherit;
|
||||
permission.checked = false;
|
||||
@@ -28,7 +30,7 @@ export class EntityPermissions extends Component {
|
||||
// Remove role row button click
|
||||
this.container.addEventListener('click', event => {
|
||||
const button = event.target.closest('button');
|
||||
if (button && button.dataset.roleId) {
|
||||
if (button && button.dataset.modelType) {
|
||||
this.removeRowOnButtonClick(button)
|
||||
}
|
||||
});
|
||||
@@ -40,6 +42,14 @@ export class EntityPermissions extends Component {
|
||||
this.addRoleRow(roleId);
|
||||
}
|
||||
});
|
||||
|
||||
// User select change
|
||||
this.userSelectContainer.querySelector('input[name="user_select"]').addEventListener('change', event => {
|
||||
const userId = event.target.value;
|
||||
if (userId) {
|
||||
this.addUserRow(userId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async addRoleRow(roleId) {
|
||||
@@ -52,23 +62,50 @@ export class EntityPermissions extends Component {
|
||||
}
|
||||
|
||||
// Get and insert new row
|
||||
const resp = await window.$http.get(`/permissions/form-row/${this.entityType}/${roleId}`);
|
||||
const resp = await window.$http.get(`/permissions/role-form-row/${this.entityType}/${roleId}`);
|
||||
const row = htmlToDom(resp.data);
|
||||
this.roleContainer.append(row);
|
||||
|
||||
this.roleSelect.disabled = false;
|
||||
}
|
||||
|
||||
async addUserRow(userId) {
|
||||
const exists = this.userContainer.querySelector(`[name^="permissions[user][${userId}]"]`) !== null;
|
||||
if (exists) {
|
||||
return;
|
||||
}
|
||||
|
||||
const toggle = this.userSelectContainer.querySelector('.dropdown-search-toggle-select');
|
||||
toggle.classList.add('disabled');
|
||||
this.userContainer.style.pointerEvents = 'none';
|
||||
|
||||
// Get and insert new row
|
||||
const resp = await window.$http.get(`/permissions/user-form-row/${this.entityType}/${userId}`);
|
||||
const row = htmlToDom(resp.data);
|
||||
this.userContainer.append(row);
|
||||
|
||||
toggle.classList.remove('disabled');
|
||||
this.userContainer.style.pointerEvents = null;
|
||||
|
||||
/** @var {UserSelect} **/
|
||||
const userSelect = window.$components.firstOnElement(this.userSelectContainer.querySelector('.dropdown-search'), 'user-select');
|
||||
userSelect.reset();
|
||||
}
|
||||
|
||||
removeRowOnButtonClick(button) {
|
||||
const row = button.closest('.item-list-row');
|
||||
const roleId = button.dataset.roleId;
|
||||
const roleName = button.dataset.roleName;
|
||||
const modelId = button.dataset.modelId;
|
||||
const modelName = button.dataset.modelName;
|
||||
const modelType = button.dataset.modelType;
|
||||
|
||||
const option = document.createElement('option');
|
||||
option.value = roleId;
|
||||
option.textContent = roleName;
|
||||
option.value = modelId;
|
||||
option.textContent = modelName;
|
||||
|
||||
if (modelType === 'role') {
|
||||
this.roleSelect.append(option);
|
||||
}
|
||||
|
||||
this.roleSelect.append(option);
|
||||
row.remove();
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,9 @@ export class UserSelect extends Component {
|
||||
this.input = this.$refs.input;
|
||||
this.userInfoContainer = this.$refs.userInfo;
|
||||
|
||||
this.initialValue = this.input.value;
|
||||
this.initialContent = this.userInfoContainer.innerHTML;
|
||||
|
||||
onChildEvent(this.container, 'a.dropdown-search-item', 'click', this.selectUser.bind(this));
|
||||
}
|
||||
|
||||
@@ -19,6 +22,13 @@ export class UserSelect extends Component {
|
||||
this.hide();
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.input.value = this.initialValue;
|
||||
this.userInfoContainer.innerHTML = this.initialContent;
|
||||
this.input.dispatchEvent(new Event('change', {bubbles: true}));
|
||||
this.hide();
|
||||
}
|
||||
|
||||
hide() {
|
||||
/** @var {Dropdown} **/
|
||||
const dropdown = window.$components.firstOnElement(this.container, 'dropdown');
|
||||
|
||||
@@ -57,12 +57,6 @@ export class KeyboardNavigationHandler {
|
||||
* @param {KeyboardEvent} event
|
||||
*/
|
||||
#keydownHandler(event) {
|
||||
|
||||
// Ignore certain key events in inputs to allow text editing.
|
||||
if (event.target.matches('input') && (event.key === 'ArrowRight' || event.key === 'ArrowLeft')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowDown' || event.key === 'ArrowRight') {
|
||||
this.focusNext();
|
||||
event.preventDefault();
|
||||
|
||||
@@ -26,7 +26,7 @@ return [
|
||||
'app_public_viewing' => 'Povolit prohlížení veřejností?',
|
||||
'app_secure_images' => 'Nahrávat obrázky neveřejně a zabezpečeně',
|
||||
'app_secure_images_toggle' => 'Zapnout bezpečnější nahrávání obrázků',
|
||||
'app_secure_images_desc' => 'Z výkonnostních důvodů jsou všechny obrázky veřejně dostupné. Tato volba přidá do adresy obrázku náhodný řetězec, aby nikdo neodhadnul adresu obrázku. Ujistěte se, že server nezobrazuje v adresáři seznam souborů, což by přístup k obrázkům opět otevřelo.',
|
||||
'app_secure_images_desc' => 'Z výkonnostních důvodů jsou všechny obrázky veřejně dostupné. Tato volba přidá do adresy obrázku náhodný řetězec, aby nikdo neodhadnul adresu obrázku. Ujistěte se, že server nezobrazuje v adresáři seznam souborů, což by přístup k přístup opět otevřelo.',
|
||||
'app_default_editor' => 'Výchozí editor',
|
||||
'app_default_editor_desc' => 'Vyberte, který editor bude použit ve výchozím nastavení při úpravách nových stránek. To může být přepsáno na úrovni stránky, kde to dovolují oprávnění.',
|
||||
'app_custom_html' => 'Vlastní obsah hlavičky HTML',
|
||||
|
||||
@@ -8,7 +8,7 @@ return [
|
||||
// Pages
|
||||
'page_create' => 'erstellte Seite',
|
||||
'page_create_notification' => 'Seite erfolgreich erstellt',
|
||||
'page_update' => 'aktualisierte Seite',
|
||||
'page_update' => 'hat die Seite aktualisiert',
|
||||
'page_update_notification' => 'Seite erfolgreich aktualisiert',
|
||||
'page_delete' => 'gelöschte Seite',
|
||||
'page_delete_notification' => 'Seite erfolgreich gelöscht',
|
||||
@@ -19,32 +19,32 @@ return [
|
||||
// Chapters
|
||||
'chapter_create' => 'erstellte Kapitel',
|
||||
'chapter_create_notification' => 'Kapitel erfolgreich erstellt',
|
||||
'chapter_update' => 'aktualisierte Kapitel',
|
||||
'chapter_update' => 'hat das Kapitel geändert',
|
||||
'chapter_update_notification' => 'Kapitel erfolgreich aktualisiert',
|
||||
'chapter_delete' => 'löschte Kapitel',
|
||||
'chapter_delete' => 'hat das Kapitel gelöscht',
|
||||
'chapter_delete_notification' => 'Kapitel erfolgreich gelöscht',
|
||||
'chapter_move' => 'verschob Kapitel',
|
||||
'chapter_move' => 'hat das Kapitel verschoben',
|
||||
|
||||
// Books
|
||||
'book_create' => 'erstellte Buch',
|
||||
'book_create' => 'hat das Buch erstellt',
|
||||
'book_create_notification' => 'Buch erfolgreich erstellt',
|
||||
'book_create_from_chapter' => 'konvertierte Kapitel zu Buch',
|
||||
'book_create_from_chapter_notification' => 'Kapitel erfolgreich in ein Buch konvertiert',
|
||||
'book_update' => 'aktualisierte Buch',
|
||||
'book_create_from_chapter' => 'umgewandeltes Kapitel zum Buch',
|
||||
'book_create_from_chapter_notification' => 'Kapitel erfolgreich in ein Buch umgewandelt',
|
||||
'book_update' => 'hat das Buch aktualisiert',
|
||||
'book_update_notification' => 'Buch erfolgreich aktualisiert',
|
||||
'book_delete' => 'löschte Buch',
|
||||
'book_delete' => 'hat das Buch gelöscht',
|
||||
'book_delete_notification' => 'Buch erfolgreich gelöscht',
|
||||
'book_sort' => 'sortierte Buch',
|
||||
'book_sort' => 'hat die Buch-Sortierung geändert',
|
||||
'book_sort_notification' => 'Das Buch wurde erfolgreich umsortiert',
|
||||
|
||||
// Bookshelves
|
||||
'bookshelf_create' => 'erstellte Regal',
|
||||
'bookshelf_create' => 'erstelltes Regal',
|
||||
'bookshelf_create_notification' => 'Regal erfolgreich erstellt',
|
||||
'bookshelf_create_from_book' => 'konvertierte Buch zu Regal',
|
||||
'bookshelf_create_from_book' => 'zu Regal konvertiertes Buch',
|
||||
'bookshelf_create_from_book_notification' => 'Buch erfolgreich in ein Regal konvertiert',
|
||||
'bookshelf_update' => 'aktualisierte Regal',
|
||||
'bookshelf_update' => 'aktualisiertes Regal',
|
||||
'bookshelf_update_notification' => 'Regal erfolgreich aktualisiert',
|
||||
'bookshelf_delete' => 'löschte Regal',
|
||||
'bookshelf_delete' => 'gelöschtes Regal',
|
||||
'bookshelf_delete_notification' => 'Regal erfolgreich gelöscht',
|
||||
|
||||
// Favourites
|
||||
|
||||
@@ -61,8 +61,8 @@ return [
|
||||
'email_confirm_send_error' => 'Leider konnte die für die Registrierung notwendige E-Mail zur Bestätigung Ihrer E-Mail-Adresse nicht versandt werden. Bitte kontaktieren Sie den Systemadministrator!',
|
||||
'email_confirm_success' => 'Ihre E-Mail wurde bestätigt! Sie sollten nun in der Lage sein, sich mit dieser E-Mail-Adresse anzumelden.',
|
||||
'email_confirm_resent' => 'Bestätigungs-E-Mail wurde erneut versendet, bitte überprüfen Sie Ihren Posteingang.',
|
||||
'email_confirm_thanks' => 'Vielen Dank für das Bestätigen!',
|
||||
'email_confirm_thanks_desc' => 'Bitte warten Sie einen Augenblick, während Ihre Bestätigung bearbeitet wird. Wenn Sie nach 3 Sekunden nicht weitergeleitet werden, drücken Sie unten den "Weiter" Link, um fortzufahren.',
|
||||
'email_confirm_thanks' => 'Thanks for confirming!',
|
||||
'email_confirm_thanks_desc' => 'Please wait a moment while your confirmation is handled. If you are not redirected after 3 seconds press the "Continue" link below to proceed.',
|
||||
|
||||
'email_not_confirmed' => 'E-Mail-Adresse ist nicht bestätigt',
|
||||
'email_not_confirmed_text' => 'Ihre E-Mail-Adresse ist bisher nicht bestätigt.',
|
||||
|
||||
@@ -25,7 +25,7 @@ return [
|
||||
'actions' => 'Aktionen',
|
||||
'view' => 'Anzeigen',
|
||||
'view_all' => 'Alle anzeigen',
|
||||
'new' => 'Neu',
|
||||
'new' => 'New',
|
||||
'create' => 'Erstellen',
|
||||
'update' => 'Aktualisieren',
|
||||
'edit' => 'Bearbeiten',
|
||||
@@ -81,14 +81,14 @@ return [
|
||||
'none' => 'Nichts',
|
||||
|
||||
// Header
|
||||
'homepage' => 'Startseite',
|
||||
'homepage' => 'Homepage',
|
||||
'header_menu_expand' => 'Header-Menü erweitern',
|
||||
'profile_menu' => 'Profilmenü',
|
||||
'view_profile' => 'Profil ansehen',
|
||||
'edit_profile' => 'Profil bearbeiten',
|
||||
'dark_mode' => 'Dunkler Modus',
|
||||
'light_mode' => 'Heller Modus',
|
||||
'global_search' => 'Globale Suche',
|
||||
'global_search' => 'Global Search',
|
||||
|
||||
// Layout tabs
|
||||
'tab_info' => 'Info',
|
||||
|
||||
@@ -66,7 +66,7 @@ return [
|
||||
'insert_link_title' => 'Link einfügen/ändern',
|
||||
'insert_horizontal_line' => 'Horizontale Linie einfügen',
|
||||
'insert_code_block' => 'Code-Block einfügen',
|
||||
'edit_code_block' => 'Code-Block bearbeiten',
|
||||
'edit_code_block' => 'Edit code block',
|
||||
'insert_drawing' => 'Zeichnung einfügen/ändern',
|
||||
'drawing_manager' => 'Zeichnungsmanager',
|
||||
'insert_media' => 'Medien einfügen/ändern',
|
||||
@@ -144,11 +144,11 @@ return [
|
||||
'url' => 'URL',
|
||||
'text_to_display' => 'Anzuzeigender Text',
|
||||
'title' => 'Titel',
|
||||
'open_link' => 'Link öffnen',
|
||||
'open_link_in' => 'Link öffnen in...',
|
||||
'open_link' => 'Open link',
|
||||
'open_link_in' => 'Open link in...',
|
||||
'open_link_current' => 'Aktuelles Fenster',
|
||||
'open_link_new' => 'Neues Fenster',
|
||||
'remove_link' => 'Link entfernen',
|
||||
'remove_link' => 'Remove link',
|
||||
'insert_collapsible' => 'Einklappbarer Block einfügen',
|
||||
'collapsible_unwrap' => 'Auspacken',
|
||||
'edit_label' => 'Label bearbeiten',
|
||||
|
||||
@@ -50,7 +50,7 @@ return [
|
||||
'permissions_role_everyone_else' => 'Alle anderen',
|
||||
'permissions_role_everyone_else_desc' => 'Berechtigungen für alle Rollen setzen, die nicht explizit überschrieben wurden.',
|
||||
'permissions_role_override' => 'Berechtigungen für Rolle überschreiben',
|
||||
'permissions_inherit_defaults' => 'Standardeinstellungen vererben',
|
||||
'permissions_inherit_defaults' => 'Inherit defaults',
|
||||
|
||||
// Search
|
||||
'search_results' => 'Suchergebnisse',
|
||||
@@ -224,8 +224,8 @@ return [
|
||||
'pages_md_insert_image' => 'Bild einfügen',
|
||||
'pages_md_insert_link' => 'Link zu einem Objekt einfügen',
|
||||
'pages_md_insert_drawing' => 'Zeichnung einfügen',
|
||||
'pages_md_show_preview' => 'Vorschau anzeigen',
|
||||
'pages_md_sync_scroll' => 'Vorschau synchronisieren',
|
||||
'pages_md_show_preview' => 'Show preview',
|
||||
'pages_md_sync_scroll' => 'Sync preview scroll',
|
||||
'pages_not_in_chapter' => 'Seite ist in keinem Kapitel',
|
||||
'pages_move' => 'Seite verschieben',
|
||||
'pages_move_success' => 'Seite nach ":parentName" verschoben',
|
||||
@@ -236,14 +236,14 @@ return [
|
||||
'pages_permissions_success' => 'Seiten Berechtigungen aktualisiert',
|
||||
'pages_revision' => 'Version',
|
||||
'pages_revisions' => 'Seitenversionen',
|
||||
'pages_revisions_desc' => 'Alle vorherhigen Revisionen dieser Seite sind unten aufgelistet. Sie können zurückschauen, vergleichen und alte Seitenversionen wiederherstellen, wenn die Berechtigungen dies erlauben. Der vollständige Verlauf der Seite kann hier möglicherweise nicht vollständig wiedergegeben werden, da je nach Systemkonfiguration alte Revisionen automatisch hätten gelöscht werden können.',
|
||||
'pages_revisions_desc' => 'Listed below are all the past revisions of this page. You can look back upon, compare, and restore old page versions if permissions allow. The full history of the page may not be fully reflected here since, depending on system configuration, old revisions could be auto-deleted.',
|
||||
'pages_revisions_named' => 'Seitenversionen von ":pageName"',
|
||||
'pages_revision_named' => 'Seitenversion von ":pageName"',
|
||||
'pages_revision_restored_from' => 'Wiederhergestellt von #:id; :summary',
|
||||
'pages_revisions_created_by' => 'Erstellt von',
|
||||
'pages_revisions_date' => 'Versionsdatum',
|
||||
'pages_revisions_number' => '#',
|
||||
'pages_revisions_sort_number' => 'Revisionsnummer',
|
||||
'pages_revisions_sort_number' => 'Revision Number',
|
||||
'pages_revisions_numbered' => 'Revision #:id',
|
||||
'pages_revisions_numbered_changes' => 'Revision #:id Änderungen',
|
||||
'pages_revisions_editor' => 'Editor-Typ',
|
||||
@@ -280,7 +280,7 @@ return [
|
||||
'shelf_tags' => 'Regal-Schlagwörter',
|
||||
'tag' => 'Schlagwort',
|
||||
'tags' => 'Schlagwörter',
|
||||
'tags_index_desc' => 'Tags können auf Inhalte im System angewendet werden, um eine flexible Form der Kategorisierung anzuwenden. Tags können sowohl einen Schlüssel als auch einen Wert haben, wobei der Wert optional ist. Einmal angewendet, können Inhalte unter Verwendung des Tag-Namens und Wertes abgefragt werden.',
|
||||
'tags_index_desc' => 'Tags can be applied to content within the system to apply a flexible form of categorization. Tags can have both a key and value, with the value being optional. Once applied, content can then be queried using the tag name and value.',
|
||||
'tag_name' => 'Schlagwort Name',
|
||||
'tag_value' => 'Inhalt (Optional)',
|
||||
'tags_explain' => "Fügen Sie Schlagwörter hinzu, um Ihren Inhalt zu kategorisieren.\nSie können einen erklärenden Inhalt hinzufügen, um eine genauere Unterteilung vorzunehmen.",
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
|
||||
return [
|
||||
'shortcuts' => 'Tastenkürzel',
|
||||
'shortcuts_interface' => 'Oberflächen-Tastaturkürzel',
|
||||
'shortcuts_toggle_desc' => 'Hier können Sie Tastaturkürzel für die Systemoberfläche für Navigation und Aktionen aktivieren oder deaktivieren.',
|
||||
'shortcuts_customize_desc' => 'Unten können Sie alle Tastenkürzel anpassen. Drücken Sie einfach die gewünschte Tastenkombination, nachdem Sie die Eingabe für eine Tastenkombination ausgewählt haben.',
|
||||
'shortcuts_toggle_label' => 'Tastaturkürzel aktiviert',
|
||||
'shortcuts_interface' => 'Interface Keyboard Shortcuts',
|
||||
'shortcuts_toggle_desc' => 'Here you can enable or disable keyboard system interface shortcuts, used for navigation and actions.',
|
||||
'shortcuts_customize_desc' => 'You can customize each of the shortcuts below. Just press your desired key combination after selecting the input for a shortcut.',
|
||||
'shortcuts_toggle_label' => 'Keyboard shortcuts enabled',
|
||||
'shortcuts_section_navigation' => 'Navigation',
|
||||
'shortcuts_section_actions' => 'Häufige Aktionen',
|
||||
'shortcuts_section_actions' => 'Gemeinsame Aktionen',
|
||||
'shortcuts_save' => 'Tastenkürzel speichern',
|
||||
'shortcuts_overlay_desc' => 'Hinweis: Wenn Tastenkürzel aktiviert sind, ist ein Hilfsoverlay durch Drücken von "?" verfügbar, welches die verfügbaren Tastenkürzel für Aktionen hervorhebt, die aktuell auf dem Bildschirm sichtbar sind.',
|
||||
'shortcuts_update_success' => 'Tastenkürzel Einstellungen wurden aktualisiert!',
|
||||
|
||||
@@ -136,11 +136,11 @@ Hinweis: Benutzer können ihre E-Mail-Adresse nach erfolgreicher Registrierung
|
||||
// Role Settings
|
||||
'roles' => 'Rollen',
|
||||
'role_user_roles' => 'Benutzer-Rollen',
|
||||
'roles_index_desc' => 'Rollen werden verwendet, um Benutzer zu gruppieren System-Berechtigung für ihre Mitglieder zuzuweisen. Wenn ein Benutzer Mitglied mehrerer Rollen ist, stapeln die gewährten Berechtigungen und der Benutzer wird alle Fähigkeiten erben.',
|
||||
'roles_x_users_assigned' => '1 Benutzer zugewiesen|:count Benutzer zugewiesen',
|
||||
'roles_x_permissions_provided' => '1 Berechtigung|:count Berechtigungen',
|
||||
'roles_assigned_users' => 'Zugewiesene Benutzer',
|
||||
'roles_permissions_provided' => 'Genutzte Berechtigungen',
|
||||
'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.',
|
||||
'roles_x_users_assigned' => '1 user assigned|:count users assigned',
|
||||
'roles_x_permissions_provided' => '1 permission|:count permissions',
|
||||
'roles_assigned_users' => 'Assigned Users',
|
||||
'roles_permissions_provided' => 'Provided Permissions',
|
||||
'role_create' => 'Neue Rolle anlegen',
|
||||
'role_create_success' => 'Rolle erfolgreich angelegt',
|
||||
'role_delete' => 'Rolle löschen',
|
||||
@@ -180,7 +180,7 @@ Hinweis: Benutzer können ihre E-Mail-Adresse nach erfolgreicher Registrierung
|
||||
|
||||
// Users
|
||||
'users' => 'Benutzer',
|
||||
'users_index_desc' => 'Erstellen und Verwalten Sie individuelle Benutzerkonten innerhalb des Systems. Benutzerkonten werden zur Anmeldung und Besitz von Inhalten und Aktivitäten verwendet. Zugriffsberechtigungen sind in erster Linie rollenbasiert, aber Besitz von Benutzerinhalten kann unter anderem auch Berechtigungen beeinflussen.',
|
||||
'users_index_desc' => 'Create & manage individual user accounts within the system. User accounts are used for login and attribution of content & activity. Access permissions are primarily role-based but user content ownership, among other factors, may also affect permissions & access.',
|
||||
'user_profile' => 'Benutzerprofil',
|
||||
'users_add_new' => 'Benutzer hinzufügen',
|
||||
'users_search' => 'Benutzer suchen',
|
||||
@@ -250,8 +250,8 @@ Hinweis: Benutzer können ihre E-Mail-Adresse nach erfolgreicher Registrierung
|
||||
|
||||
// Webhooks
|
||||
'webhooks' => 'Webhooks',
|
||||
'webhooks_index_desc' => 'Webhooks sind eine Möglichkeit, Daten an externe URLs zu senden, wenn bestimmte Aktionen und Ereignisse im System auftreten, was eine ereignisbasierte Integration mit externen Plattformen wie Messaging- oder Benachrichtigungssystemen ermöglicht.',
|
||||
'webhooks_x_trigger_events' => '1 Triggerereignis|:count Triggerereignisse',
|
||||
'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.',
|
||||
'webhooks_x_trigger_events' => '1 trigger event|:count trigger events',
|
||||
'webhooks_create' => 'Neuen Webhook erstellen',
|
||||
'webhooks_none_created' => 'Es wurden noch keine Webhooks erstellt.',
|
||||
'webhooks_edit' => 'Webhook bearbeiten',
|
||||
|
||||
@@ -30,10 +30,10 @@ return [
|
||||
'dont_have_account' => 'Noch kein Konto erstellt?',
|
||||
'social_login' => 'Mit Sozialem Netzwerk anmelden',
|
||||
'social_registration' => 'Mit Sozialem Netzwerk registrieren',
|
||||
'social_registration_text' => 'Mit einem dieser Dienste registrieren oder anmelden',
|
||||
'social_registration_text' => 'Mit einer dieser Dienste registrieren oder anmelden',
|
||||
|
||||
'register_thanks' => 'Vielen Dank für deine Registrierung!',
|
||||
'register_confirm' => 'Bitte prüfe deinen Posteingang und bestätige die Registrierung.',
|
||||
'register_confirm' => 'Bitte prüfe Deinen Posteingang und bestätig die Registrierung.',
|
||||
'registrations_disabled' => 'Eine Registrierung ist momentan nicht möglich',
|
||||
'registration_email_domain_invalid' => 'Du kannst dich mit dieser E-Mail nicht registrieren.',
|
||||
'register_success' => 'Vielen Dank für deine Registrierung! Du bist jetzt registriert und eingeloggt.',
|
||||
@@ -45,12 +45,12 @@ return [
|
||||
|
||||
// Password Reset
|
||||
'reset_password' => 'Passwort vergessen',
|
||||
'reset_password_send_instructions' => 'Bitte gib Deine E-Mail-Adresse ein. Danach erhältst Du eine E-Mail mit einem Link zum Zurücksetzen deines Passwortes.',
|
||||
'reset_password_send_instructions' => 'Bitte gib Deine E-Mail-Adresse ein. Danach erhältst Du eine E-Mail mit einem Link zum Zurücksetzen Deines Passwortes.',
|
||||
'reset_password_send_button' => 'Passwort zurücksetzen',
|
||||
'reset_password_sent' => 'Ein Link zum Zurücksetzen des Passworts wird an :email gesendet, wenn diese E-Mail-Adresse im System gefunden wird.',
|
||||
'reset_password_success' => 'Dein Passwort wurde erfolgreich zurückgesetzt.',
|
||||
'email_reset_subject' => 'Passwort zurücksetzen für :appName',
|
||||
'email_reset_text' => 'Du erhältst diese E-Mail, weil jemand versucht hat, dein Passwort zurückzusetzen.',
|
||||
'email_reset_text' => 'Du erhältst diese E-Mail, weil jemand versucht hat, Dein Passwort zurückzusetzen.',
|
||||
'email_reset_not_requested' => 'Wenn du das Zurücksetzen des Passworts nicht angefordert hast, ist keine weitere Aktion erforderlich.',
|
||||
|
||||
// Email Confirmation
|
||||
@@ -58,15 +58,15 @@ return [
|
||||
'email_confirm_greeting' => 'Danke, dass Du dich für :appName registrierst hast!',
|
||||
'email_confirm_text' => 'Bitte bestätige Deine E-Mail-Adresse, indem Du auf die Schaltfläche klickst:',
|
||||
'email_confirm_action' => 'E-Mail-Adresse bestätigen',
|
||||
'email_confirm_send_error' => 'Leider konnte die für die Registrierung notwendige E-Mail zur Bestätigung deiner E-Mail-Adresse nicht versandt werden. Bitte kontaktiere deinen Systemadministrator!',
|
||||
'email_confirm_success' => 'Deine E-Mail Adresse wurde bestätigt! Du solltest nun in der Lage sein, dich mit deiner E-Mail-Adresse anzumelden.',
|
||||
'email_confirm_resent' => 'Bestätigungs-E-Mail wurde erneut versendet, bitte überprüfe deinen Posteingang.',
|
||||
'email_confirm_thanks' => 'Vielen Dank für das Bestätigen!',
|
||||
'email_confirm_thanks_desc' => 'Bitte warte einen Augenblick, während deine Bestätigung bearbeitet wird. Wenn Du nach 3 Sekunden nicht weitergeleitet wirst, drücke unten den "Weiter" Link, um fortzufahren.',
|
||||
'email_confirm_send_error' => 'Leider konnte die für die Registrierung notwendige E-Mail zur Bestätigung Deiner E-Mail-Adresse nicht versandt werden. Bitte kontaktiere den Systemadministrator!',
|
||||
'email_confirm_success' => 'Ihre E-Mail wurde bestätigt! Sie sollten nun in der Lage sein, sich mit dieser E-Mail-Adresse anzumelden.',
|
||||
'email_confirm_resent' => 'Bestätigungs-E-Mail wurde erneut versendet, bitte überprüfe Deinen Posteingang.',
|
||||
'email_confirm_thanks' => 'Thanks for confirming!',
|
||||
'email_confirm_thanks_desc' => 'Please wait a moment while your confirmation is handled. If you are not redirected after 3 seconds press the "Continue" link below to proceed.',
|
||||
|
||||
'email_not_confirmed' => 'E-Mail-Adresse ist nicht bestätigt',
|
||||
'email_not_confirmed_text' => 'Deine E-Mail-Adresse ist bisher nicht bestätigt.',
|
||||
'email_not_confirmed_click_link' => 'Bitte klicke auf den Link in der E-Mail, die du nach der Registrierung erhalten hast.',
|
||||
'email_not_confirmed_click_link' => 'Bitte klicke auf den Link in der E-Mail, die Du nach der Registrierung erhalten hast.',
|
||||
'email_not_confirmed_resend' => 'Wenn Du die E-Mail nicht erhalten hast, kannst Du die Nachricht erneut anfordern. Fülle hierzu bitte das folgende Formular aus:',
|
||||
'email_not_confirmed_resend_button' => 'Bestätigungs-E-Mail erneut senden',
|
||||
|
||||
@@ -76,30 +76,30 @@ return [
|
||||
'user_invite_email_text' => 'Klicke auf die Schaltfläche unten, um ein Passwort festzulegen und Zugriff zu erhalten:',
|
||||
'user_invite_email_action' => 'Konto-Passwort festlegen',
|
||||
'user_invite_page_welcome' => 'Willkommen bei :appName!',
|
||||
'user_invite_page_text' => 'Um die Anmeldung abzuschließen und Zugriff auf :appName zu bekommen, muss noch ein Passwort festgelegt werden. Dieses wird in Zukunft für die Anmeldung benötigt.',
|
||||
'user_invite_page_text' => 'Um die Anmeldung abzuschließen und Zugriff auf :appName zu bekommen muss noch ein Passwort festgelegt werden. Dieses wird in Zukunft zum Einloggen benötigt.',
|
||||
'user_invite_page_confirm_button' => 'Passwort bestätigen',
|
||||
'user_invite_success_login' => 'Passwort gesetzt, du solltest nun in der Lage sein, dich mit deinem Passwort an :appName anzumelden!',
|
||||
'user_invite_success_login' => 'Passwort gesetzt, Sie sollten nun in der Lage sein, sich mit Ihrem Passwort an :appName anzumelden!',
|
||||
|
||||
// Multi-factor Authentication
|
||||
'mfa_setup' => 'Multi-Faktor-Authentifizierung einrichten',
|
||||
'mfa_setup_desc' => 'Richte eine Multi-Faktor-Authentifizierung als zusätzliche Sicherheitsstufe für dein Benutzerkonto ein.',
|
||||
'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' => 'Bist du sicher, dass du diese Multi-Faktor-Authentifizierungsmethode entfernen möchtest?',
|
||||
'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' => 'Du hast weniger als 5 Backup-Codes übrig. Bitte erstelle und speichere einen neuen Satz, bevor Du keine Codes mehr hast, um zu verhindern, dass du von deinem Konto ausgesperrt wirst.',
|
||||
'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ötigst du eine mobile Anwendung, die TOTP unterstützt, wie Google Authenticator, Authy oder Microsoft Authenticator.',
|
||||
'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' => 'Speichere eine Reihe von einmaligen Backup-Codes an einem sicheren Ort. Du kannst damit deine identität bestätigen.',
|
||||
'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' => 'Speichere die folgende Liste der Codes an einem sicheren Ort. Wenn du auf das System zugreifst, kannst du einen der Codes als zweiten Authentifizierungsmechanismus verwenden.',
|
||||
'mfa_gen_backup_codes_download' => 'Codes herunterladen',
|
||||
'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ötigst du eine mobile Anwendung, die TOTP unterstützt, wie Google Authenticator, Authy oder Microsoft Authenticator.',
|
||||
'mfa_gen_totp_scan' => 'Scanne den QR-Code unten mit deiner bevorzugten Authentifizierungs-App, um zu beginnen.',
|
||||
'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üfe, dass alles funktioniert, indem du einen Code aus deiner Authentifizierungs-App in das Eingabefeld unten eingibst:',
|
||||
'mfa_gen_totp_provide_code_here' => 'Gib hier den von der App generierten Code ein',
|
||||
|
||||
@@ -25,7 +25,7 @@ return [
|
||||
'actions' => 'Aktionen',
|
||||
'view' => 'Anzeigen',
|
||||
'view_all' => 'Alle anzeigen',
|
||||
'new' => 'Neu',
|
||||
'new' => 'New',
|
||||
'create' => 'Anlegen',
|
||||
'update' => 'Aktualisieren',
|
||||
'edit' => 'Bearbeiten',
|
||||
@@ -81,14 +81,14 @@ return [
|
||||
'none' => 'Keine',
|
||||
|
||||
// Header
|
||||
'homepage' => 'Startseite',
|
||||
'homepage' => 'Homepage',
|
||||
'header_menu_expand' => 'Header-Menü erweitern',
|
||||
'profile_menu' => 'Profilmenü',
|
||||
'view_profile' => 'Profil ansehen',
|
||||
'edit_profile' => 'Profil bearbeiten',
|
||||
'dark_mode' => 'Dunkler Modus',
|
||||
'light_mode' => 'Heller Modus',
|
||||
'global_search' => 'Globale Suche',
|
||||
'global_search' => 'Global Search',
|
||||
|
||||
// Layout tabs
|
||||
'tab_info' => 'Info',
|
||||
|
||||
@@ -14,7 +14,7 @@ return [
|
||||
'image_uploaded' => 'Hochgeladen am :uploadedDate',
|
||||
'image_load_more' => 'Mehr',
|
||||
'image_image_name' => 'Bildname',
|
||||
'image_delete_used' => 'Dieses Bild wird auf den folgenden Seiten benutzt.',
|
||||
'image_delete_used' => 'Dieses Bild wird auf den folgenden Seiten benutzt. ',
|
||||
'image_delete_confirm_text' => 'Bist Du sicher, dass Du diese Seite löschen möchtest?',
|
||||
'image_select_image' => 'Bild auswählen',
|
||||
'image_dropzone' => 'Ziehe Bilder hierher oder klicke hier, um ein Bild auszuwählen',
|
||||
|
||||
@@ -66,7 +66,7 @@ return [
|
||||
'insert_link_title' => 'Link einfügen/bearbeiten',
|
||||
'insert_horizontal_line' => 'Horizontale Linie einfügen',
|
||||
'insert_code_block' => 'Codeblock einfügen',
|
||||
'edit_code_block' => 'Codeblock bearbeiten',
|
||||
'edit_code_block' => 'Edit code block',
|
||||
'insert_drawing' => 'Zeichnung einfügen/bearbeiten',
|
||||
'drawing_manager' => 'Zeichnungsmanager',
|
||||
'insert_media' => 'Medien einfügen/bearbeiten',
|
||||
@@ -121,7 +121,7 @@ return [
|
||||
'paste_column_before' => 'Spalte davor einfügen',
|
||||
'paste_column_after' => 'Spalte danach einfügen',
|
||||
'cell_padding' => 'Zellenabstand',
|
||||
'cell_spacing' => 'Zellenaußenabstand',
|
||||
'cell_spacing' => 'Zellen-Außenabstand',
|
||||
'caption' => 'Überschrift',
|
||||
'show_caption' => 'Überschrift anzeigen',
|
||||
'constrain' => 'Proportionen festsetzen',
|
||||
@@ -144,11 +144,11 @@ return [
|
||||
'url' => 'URL',
|
||||
'text_to_display' => 'Anzuzeigender Text',
|
||||
'title' => 'Titel',
|
||||
'open_link' => 'Link öffnen',
|
||||
'open_link_in' => 'Link öffnen in...',
|
||||
'open_link' => 'Open link',
|
||||
'open_link_in' => 'Open link in...',
|
||||
'open_link_current' => 'Aktuellem Fenster',
|
||||
'open_link_new' => 'Neuem Fenster',
|
||||
'remove_link' => 'Link entfernen',
|
||||
'remove_link' => 'Remove link',
|
||||
'insert_collapsible' => 'Einklappbaren Block einfügen',
|
||||
'collapsible_unwrap' => 'Entfernen',
|
||||
'edit_label' => 'Beschriftung bearbeiten',
|
||||
|
||||
@@ -38,11 +38,11 @@ return [
|
||||
'export_html' => 'HTML-Datei',
|
||||
'export_pdf' => 'PDF-Datei',
|
||||
'export_text' => 'Textdatei',
|
||||
'export_md' => 'Markdown-Datei',
|
||||
'export_md' => 'Markdown-Dateir',
|
||||
|
||||
// Permissions and restrictions
|
||||
'permissions' => 'Berechtigungen',
|
||||
'permissions_desc' => 'Lege hier Berechtigungen fest, um die Standardberechtigungen von Benutzerrollen zu überschreiben.',
|
||||
'permissions_desc' => 'Legen Sie hier Berechtigungen fest, um die Standardberechtigungen von Benutzerrollen zu überschreiben.',
|
||||
'permissions_book_cascade' => 'In Büchern festgelegte Berechtigungen werden automatisch in untergeordnete Kapitel und Seiten kaskadiert, es sei denn, sie haben eigene Berechtigungen definiert.',
|
||||
'permissions_chapter_cascade' => 'In Kapiteln festgelegte Berechtigungen werden automatisch in untergeordnete Seiten kaskadiert, es sei denn, sie haben eigene Berechtigungen definiert.',
|
||||
'permissions_save' => 'Berechtigungen speichern',
|
||||
@@ -50,7 +50,7 @@ return [
|
||||
'permissions_role_everyone_else' => 'Alle anderen',
|
||||
'permissions_role_everyone_else_desc' => 'Berechtigungen für alle Rollen setzen, die nicht explizit überschrieben wurden.',
|
||||
'permissions_role_override' => 'Berechtigungen für Rolle überschreiben',
|
||||
'permissions_inherit_defaults' => 'Standardeinstellungen vererben',
|
||||
'permissions_inherit_defaults' => 'Inherit defaults',
|
||||
|
||||
// Search
|
||||
'search_results' => 'Suchergebnisse',
|
||||
@@ -71,7 +71,7 @@ return [
|
||||
'search_created_by_me' => 'Von mir erstellt',
|
||||
'search_updated_by_me' => 'Von mir aktualisiert',
|
||||
'search_owned_by_me' => 'Besitzt von mir',
|
||||
'search_date_options' => 'Datumsoptionen',
|
||||
'search_date_options' => 'Datums Optionen',
|
||||
'search_updated_before' => 'Aktualisiert vor',
|
||||
'search_updated_after' => 'Aktualisiert nach',
|
||||
'search_created_before' => 'Erstellt vor',
|
||||
@@ -93,7 +93,7 @@ return [
|
||||
'shelves_save' => 'Regal speichern',
|
||||
'shelves_books' => 'Bücher in diesem Regal',
|
||||
'shelves_add_books' => 'Buch zu diesem Regal hinzufügen',
|
||||
'shelves_drag_books' => 'Ziehe die Bücher nach unten, um sie zu diesem Regal hinzuzufügen',
|
||||
'shelves_drag_books' => 'Ziehen die Bücher nach unten, um sie zu diesem Regal hinzuzufügen',
|
||||
'shelves_empty_contents' => 'Diesem Regal sind keine Bücher zugewiesen',
|
||||
'shelves_edit_and_assign' => 'Regal bearbeiten um Bücher hinzuzufügen',
|
||||
'shelves_edit_named' => 'Regal :name bearbeiten',
|
||||
@@ -126,7 +126,7 @@ return [
|
||||
'books_delete' => 'Buch löschen',
|
||||
'books_delete_named' => 'Buch ":bookName" löschen',
|
||||
'books_delete_explain' => 'Das Buch ":bookName" wird gelöscht und alle zugehörigen Kapitel und Seiten entfernt.',
|
||||
'books_delete_confirmation' => 'Bist Du sicher, dass du dieses Buch löschen möchtest?',
|
||||
'books_delete_confirmation' => 'Bist Du sicher, dass Du dieses Buch löschen möchtest?',
|
||||
'books_edit' => 'Buch bearbeiten',
|
||||
'books_edit_named' => 'Buch ":bookName" bearbeiten',
|
||||
'books_form_book_name' => 'Name des Buches',
|
||||
@@ -161,8 +161,8 @@ return [
|
||||
'chapters_create' => 'Neues Kapitel anlegen',
|
||||
'chapters_delete' => 'Kapitel entfernen',
|
||||
'chapters_delete_named' => 'Kapitel ":chapterName" entfernen',
|
||||
'chapters_delete_explain' => 'Hiermit löscht du das Kapitel mit dem Namen \':chapterName\'. Alle Seiten, die innerhalb dieses Kapitels existieren, werden ebenfalls gelöscht.',
|
||||
'chapters_delete_confirm' => 'Bist du sicher, dass du dieses Kapitel löschen möchtest?',
|
||||
'chapters_delete_explain' => 'Hiermit löschen Sie das Kapitel mit dem Namen \':chapterName\'. Alle Seiten, die innerhalb dieses Kapitels existieren, werden ebenfalls gelöscht.',
|
||||
'chapters_delete_confirm' => 'Bist Du sicher, dass Du dieses Kapitel löschen möchtest?',
|
||||
'chapters_edit' => 'Kapitel bearbeiten',
|
||||
'chapters_edit_named' => 'Kapitel ":chapterName" bearbeiten',
|
||||
'chapters_save' => 'Kapitel speichern',
|
||||
@@ -192,8 +192,8 @@ return [
|
||||
'pages_delete_draft' => 'Seitenentwurf löschen',
|
||||
'pages_delete_success' => 'Seite gelöscht',
|
||||
'pages_delete_draft_success' => 'Seitenentwurf gelöscht',
|
||||
'pages_delete_confirm' => 'Bist du sicher, dass du diese Seite löschen möchtest?',
|
||||
'pages_delete_draft_confirm' => 'Bist du sicher, dass du diesen Seitenentwurf löschen möchtest?',
|
||||
'pages_delete_confirm' => 'Bist Du sicher, dass Du diese Seite löschen möchtest?',
|
||||
'pages_delete_draft_confirm' => 'Bist Du sicher, dass Du diesen Seitenentwurf löschen möchtest?',
|
||||
'pages_editing_named' => 'Seite ":pageName" bearbeiten',
|
||||
'pages_edit_draft_options' => 'Entwurfsoptionen',
|
||||
'pages_edit_save_draft' => 'Entwurf speichern',
|
||||
@@ -208,11 +208,11 @@ return [
|
||||
'pages_edit_switch_to_markdown_stable' => '(Stabiler Inhalt)',
|
||||
'pages_edit_switch_to_wysiwyg' => 'Zum WYSIWYG Editor wechseln',
|
||||
'pages_edit_set_changelog' => 'Änderungsprotokoll hinzufügen',
|
||||
'pages_edit_enter_changelog_desc' => 'Bitte gib eine kurze Zusammenfassung deiner Änderungen ein',
|
||||
'pages_edit_enter_changelog_desc' => 'Bitte gib eine kurze Zusammenfassung Deiner Änderungen ein',
|
||||
'pages_edit_enter_changelog' => 'Änderungsprotokoll eingeben',
|
||||
'pages_editor_switch_title' => 'Editor wechseln',
|
||||
'pages_editor_switch_are_you_sure' => 'Bist du dir sicher, dass du den Editor für diese Seite ändern willst?',
|
||||
'pages_editor_switch_consider_following' => 'Beachte beim Wechsel des Editors folgendes:',
|
||||
'pages_editor_switch_consider_following' => 'Beachte beim Wechsel des Editors Folgendes:',
|
||||
'pages_editor_switch_consideration_a' => 'Nach dem Speichern wird der neue Editor von allen zukünftigen Bearbeitern verwendet, auch von denen, die den Editortyp nicht selbst ändern können.',
|
||||
'pages_editor_switch_consideration_b' => 'Dies kann unter Umständen zu einem Verlust von Details und Syntax führen.',
|
||||
'pages_editor_switch_consideration_c' => 'Tag- oder Changelog-Änderungen, die seit dem letzten Speichern vorgenommen wurden, bleiben bei dieser Änderung nicht erhalten.',
|
||||
@@ -224,8 +224,8 @@ return [
|
||||
'pages_md_insert_image' => 'Bild einfügen',
|
||||
'pages_md_insert_link' => 'Link zu einem Objekt einfügen',
|
||||
'pages_md_insert_drawing' => 'Zeichnung einfügen',
|
||||
'pages_md_show_preview' => 'Vorschau anzeigen',
|
||||
'pages_md_sync_scroll' => 'Vorschau synchronisieren',
|
||||
'pages_md_show_preview' => 'Show preview',
|
||||
'pages_md_sync_scroll' => 'Sync preview scroll',
|
||||
'pages_not_in_chapter' => 'Seite ist in keinem Kapitel',
|
||||
'pages_move' => 'Seite verschieben',
|
||||
'pages_move_success' => 'Seite nach ":parentName" verschoben',
|
||||
@@ -236,14 +236,14 @@ return [
|
||||
'pages_permissions_success' => 'Seiten Berechtigungen aktualisiert',
|
||||
'pages_revision' => 'Version',
|
||||
'pages_revisions' => 'Seitenversionen',
|
||||
'pages_revisions_desc' => 'Alle vorherhigen Revisionen dieser Seite sind unten aufgelistet. Du kannst zurückschauen, vergleichen und alte Seitenversionen wiederherstellen, wenn die Berechtigungen dies erlauben. Der vollständige Verlauf der Seite kann hier möglicherweise nicht vollständig wiedergegeben werden, da je nach Systemkonfiguration alte Revisionen automatisch gelöscht werden könnten.',
|
||||
'pages_revisions_desc' => 'Listed below are all the past revisions of this page. You can look back upon, compare, and restore old page versions if permissions allow. The full history of the page may not be fully reflected here since, depending on system configuration, old revisions could be auto-deleted.',
|
||||
'pages_revisions_named' => 'Seitenversionen von ":pageName"',
|
||||
'pages_revision_named' => 'Seitenversion von ":pageName"',
|
||||
'pages_revision_restored_from' => 'Wiederhergestellt von #:id; :summary',
|
||||
'pages_revisions_created_by' => 'Erstellt von',
|
||||
'pages_revisions_date' => 'Versionsdatum',
|
||||
'pages_revisions_number' => '#',
|
||||
'pages_revisions_sort_number' => 'Revisionsnummer',
|
||||
'pages_revisions_sort_number' => 'Revision Number',
|
||||
'pages_revisions_numbered' => 'Revision #:id',
|
||||
'pages_revisions_numbered_changes' => 'Revision #:id Änderungen',
|
||||
'pages_revisions_editor' => 'Editortyp',
|
||||
@@ -260,7 +260,7 @@ return [
|
||||
'pages_references_update_revision' => 'Automatische Systemaktualisierung interner Links',
|
||||
'pages_initial_name' => 'Neue Seite',
|
||||
'pages_editing_draft_notification' => 'Du bearbeitest momenten einen Entwurf, der zuletzt :timeDiff gespeichert wurde.',
|
||||
'pages_draft_edited_notification' => 'Diese Seite wurde seit diesem Zeitpunkt verändert. Wir empfehlen diesen Entwurf zu verwerfen.',
|
||||
'pages_draft_edited_notification' => 'Diese Seite wurde seit diesem Zeitpunkt verändert. Wir empfehlen Ihnen, diesen Entwurf zu verwerfen.',
|
||||
'pages_draft_page_changed_since_creation' => 'Diese Seite wurde seit der Erstellung dieses Entwurfs aktualisiert. Es wird empfohlen, diesen Entwurf zu verwerfen oder darauf zu achten, dass keine Seitenänderungen überschrieben werden.',
|
||||
'pages_draft_edit_active' => [
|
||||
'start_a' => ':count Benutzer bearbeiten derzeit diese Seite.',
|
||||
@@ -280,7 +280,7 @@ return [
|
||||
'shelf_tags' => 'Regal-Schlagwörter',
|
||||
'tag' => 'Schlagwort',
|
||||
'tags' => 'Schlagwörter',
|
||||
'tags_index_desc' => 'Tags können auf Inhalte im System angewendet werden, um eine flexible Form der Kategorisierung anzuwenden. Tags können sowohl einen Schlüssel als auch einen Wert haben, wobei der Wert optional ist. Einmal angewendet, können Inhalte unter Verwendung des Tag-Namens und Wertes abgefragt werden.',
|
||||
'tags_index_desc' => 'Tags can be applied to content within the system to apply a flexible form of categorization. Tags can have both a key and value, with the value being optional. Once applied, content can then be queried using the tag name and value.',
|
||||
'tag_name' => 'Schlagwort Name',
|
||||
'tag_value' => 'Inhalt (Optional)',
|
||||
'tags_explain' => "Füge Schlagwörter hinzu, um ihren Inhalt zu kategorisieren.\nDu kannst einen erklärenden Inhalt hinzufügen, um eine genauere Unterteilung vorzunehmen.",
|
||||
@@ -297,16 +297,16 @@ return [
|
||||
'tags_view_existing_tags' => 'Vorhandene Tags anzeigen',
|
||||
'tags_list_empty_hint' => 'Tags können über die Seitenleiste des Seiteneditors oder bei der Bearbeitung der Details eines Buches, Kapitels oder Regals vergeben werden.',
|
||||
'attachments' => 'Anhänge',
|
||||
'attachments_explain' => 'Du kannst auf deiner Seite Dateien hochladen oder Links hinzufügen. Diese werden in der Seitenleiste angezeigt.',
|
||||
'attachments_explain' => 'Du kannst auf Deiner Seite Dateien hochladen oder Links hinzufügen. Diese werden in der Seitenleiste angezeigt.',
|
||||
'attachments_explain_instant_save' => 'Änderungen werden direkt gespeichert.',
|
||||
'attachments_items' => 'Angefügte Elemente',
|
||||
'attachments_upload' => 'Datei hochladen',
|
||||
'attachments_link' => 'Link hinzufügen',
|
||||
'attachments_set_link' => 'Link setzen',
|
||||
'attachments_delete' => 'Bist du sicher, dass du diesen Anhang löschen möchtest?',
|
||||
'attachments_delete' => 'Bist Du sicher, dass Du diesen Anhang löschen möchtest?',
|
||||
'attachments_dropzone' => 'Ziehe Dateien hierher oder klicke hier, um eine Datei auszuwählen',
|
||||
'attachments_no_files' => 'Es wurden bisher keine Dateien hochgeladen.',
|
||||
'attachments_explain_link' => 'Wenn du keine Datei hochladen möchtest, kannst du stattdessen einen Link hinzufügen. Dieser Link kann auf eine andere Seite oder eine Datei im Internet verweisen.',
|
||||
'attachments_explain_link' => 'Wenn Du keine Datei hochladen möchtest, kannst Du stattdessen einen Link hinzufügen. Dieser Link kann auf eine andere Seite oder eine Datei im Internet verweisen.',
|
||||
'attachments_link_name' => 'Link-Name',
|
||||
'attachment_link' => 'Link zum Anhang',
|
||||
'attachments_link_url' => 'Link zu einer Datei',
|
||||
@@ -341,7 +341,7 @@ return [
|
||||
'comment' => 'Kommentar',
|
||||
'comments' => 'Kommentare',
|
||||
'comment_add' => 'Kommentieren',
|
||||
'comment_placeholder' => 'Gib hier deine Kommentare ein (Markdown unterstützt)',
|
||||
'comment_placeholder' => 'Gib hier Deine Kommentare ein (Markdown unterstützt)',
|
||||
'comment_count' => '{0} Keine Kommentare|{1} 1 Kommentar|[2,*] :count Kommentare',
|
||||
'comment_save' => 'Kommentar speichern',
|
||||
'comment_saving' => 'Kommentar wird gespeichert...',
|
||||
@@ -352,12 +352,12 @@ return [
|
||||
'comment_deleted_success' => 'Kommentar gelöscht',
|
||||
'comment_created_success' => 'Kommentar hinzugefügt',
|
||||
'comment_updated_success' => 'Kommentar aktualisiert',
|
||||
'comment_delete_confirm' => 'Möchtst du diesen Kommentar wirklich löschen?',
|
||||
'comment_delete_confirm' => 'Möchtst Du diesen Kommentar wirklich löschen?',
|
||||
'comment_in_reply_to' => 'Antwort auf :commentId',
|
||||
|
||||
// Revision
|
||||
'revision_delete_confirm' => 'Bist du sicher, dass du diese Revision löschen möchtest?',
|
||||
'revision_restore_confirm' => 'Bist du sicher, dass du diese Revision wiederherstellen willst? Der aktuelle Seiteninhalt wird ersetzt.',
|
||||
'revision_delete_confirm' => 'Bist Du sicher, dass Du diese Revision löschen möchtest?',
|
||||
'revision_restore_confirm' => 'Sind Sie sicher, dass Sie diese Revision wiederherstellen wollen? Der aktuelle Seiteninhalt wird ersetzt.',
|
||||
'revision_delete_success' => 'Revision gelöscht',
|
||||
'revision_cannot_delete_latest' => 'Die letzte Version kann nicht gelöscht werden.',
|
||||
|
||||
|
||||
@@ -14,14 +14,14 @@ return [
|
||||
'email_confirmation_invalid' => 'Der Bestätigungslink ist nicht gültig oder wurde bereits verwendet. Bitte registriere dich 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 fehlgeschlagen',
|
||||
'ldap_fail_anonymous' => 'Anonymer LDAP-Zugriff ist fehlgeschlafgen',
|
||||
'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',
|
||||
'saml_already_logged_in' => 'Du bist bereits angemeldet',
|
||||
'saml_user_not_registered' => 'Kein Benutzer mit ID :name registriert und die automatische Registrierung ist deaktiviert',
|
||||
'saml_no_email_address' => 'Es konnte keine E-Mail-Adresse für diesen Benutzer in den vom externen Authentifizierungssystem zur Verfügung gestellten Daten gefunden werden',
|
||||
'saml_invalid_response_id' => 'Die Anfrage vom externen Authentifizierungssystem wird von einem von dieser Anwendung gestarteten Prozess nicht erkannt. Das Zurückblättern nach einem Login könnte dieses Problem verursachen.',
|
||||
'saml_invalid_response_id' => 'Die Anfrage vom externen Authentifizierungssystem wird von einem von dieser Anwendung gestarteten Prozess nicht erkannt. Das Zurückgehen nach einem Login könnte dieses Problem verursachen.',
|
||||
'saml_fail_authed' => 'Anmeldung mit :system fehlgeschlagen, System konnte keine erfolgreiche Autorisierung bereitstellen',
|
||||
'oidc_already_logged_in' => 'Bereits angemeldet',
|
||||
'oidc_user_not_registered' => 'Der Benutzer :name ist nicht registriert und die automatische Registrierung ist deaktiviert',
|
||||
@@ -30,13 +30,13 @@ return [
|
||||
'social_no_action_defined' => 'Es ist keine Aktion definiert',
|
||||
'social_login_bad_response' => "Fehler bei :socialAccount Login: \n:error",
|
||||
'social_account_in_use' => 'Dieses :socialAccount-Konto wird bereits verwendet. Bitte melde dich mit dem :socialAccount-Konto an.',
|
||||
'social_account_email_in_use' => 'Die E-Mail-Adresse ":email" ist bereits registriert. Wenn du bereits registriert bist, kannst du Dein :socialAccount-Konto in Deinen Profil-Einstellungen verknüpfen.',
|
||||
'social_account_existing' => 'Dieses :socialAccount-Konto ist bereits mit deinem Profil verknüpft.',
|
||||
'social_account_email_in_use' => 'Die E-Mail-Adresse ":email" ist bereits registriert. Wenn Du bereits registriert bist, kannst Du Dein :socialAccount-Konto in Deinen Profil-Einstellungen verknüpfen.',
|
||||
'social_account_existing' => 'Dieses :socialAccount-Konto ist bereits mit Ihrem Profil verknüpft.',
|
||||
'social_account_already_used_existing' => 'Dieses :socialAccount-Konto wird bereits von einem anderen Benutzer verwendet.',
|
||||
'social_account_not_used' => 'Dieses :socialAccount-Konto ist bisher keinem Benutzer zugeordnet. Du kannst das in deinen Profil-Einstellungen tun.',
|
||||
'social_account_register_instructions' => 'Wenn du bisher kein Social-Media Konto besitzt, kannst du ein solches Konto mit der :socialAccount Option anlegen.',
|
||||
'social_account_not_used' => 'Dieses :socialAccount-Konto ist bisher keinem Benutzer zugeordnet. Du kannst das in Deinen Profil-Einstellungen tun.',
|
||||
'social_account_register_instructions' => 'Wenn Du bisher kein Social-Media Konto besitzt, kannst Du ein solches Konto mit der :socialAccount Option anlegen.',
|
||||
'social_driver_not_found' => 'Treiber für Social-Media-Konten nicht gefunden',
|
||||
'social_driver_not_configured' => 'Dein :socialAccount-Konto ist nicht korrekt konfiguriert.',
|
||||
'social_driver_not_configured' => 'Ihr :socialAccount-Konto ist nicht korrekt konfiguriert.',
|
||||
'invite_token_expired' => 'Dieser Einladungslink ist abgelaufen. Du kannst stattdessen versuchen, dein Passwort zurückzusetzen.',
|
||||
|
||||
// System
|
||||
@@ -44,7 +44,7 @@ return [
|
||||
'cannot_get_image_from_url' => 'Bild konnte nicht von der URL :url geladen werden.',
|
||||
'cannot_create_thumbs' => 'Der Server kann keine Vorschau-Bilder erzeugen. Bitte prüfe, ob die GD PHP-Erweiterung installiert ist.',
|
||||
'server_upload_limit' => 'Der Server verbietet das Hochladen von Dateien mit dieser Dateigröße. Bitte versuche es mit einer kleineren Datei.',
|
||||
'uploaded' => 'Der Server verbietet das Hochladen von Dateien mit dieser Dateigröße. Bitte versuche es mit einer kleineren Datei.',
|
||||
'uploaded' => 'Der Server verbietet das Hochladen von Dateien mit dieser Dateigröße. Bitte versuchen Sie es mit einer kleineren Datei.',
|
||||
'image_upload_error' => 'Beim Hochladen des Bildes trat ein Fehler auf.',
|
||||
'image_upload_type_error' => 'Der Bildtyp der hochgeladenen Datei ist ungültig.',
|
||||
'file_upload_timeout' => 'Der Upload der Datei ist abgelaufen.',
|
||||
@@ -53,7 +53,7 @@ return [
|
||||
'attachment_not_found' => 'Anhang konnte nicht gefunden werden.',
|
||||
|
||||
// Pages
|
||||
'page_draft_autosave_fail' => 'Fehler beim Speichern des Entwurfs. Stelle sicher, dass du mit dem Internet verbunden bist, bevor du den Entwurf dieser Seite speicherst.',
|
||||
'page_draft_autosave_fail' => 'Fehler beim Speichern des Entwurfs. Stelle sicher, dass Du mit dem Internet verbunden bist, bevor Du den Entwurf dieser Seite speicherst.',
|
||||
'page_custom_home_deletion' => 'Eine als Startseite gesetzte Seite kann nicht gelöscht werden.',
|
||||
|
||||
// Entities
|
||||
@@ -74,7 +74,7 @@ return [
|
||||
'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. Ordne die Administratorrolle einem anderen Benutzer zu, bevor du versuchst, 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.',
|
||||
@@ -85,7 +85,7 @@ return [
|
||||
|
||||
// Error pages
|
||||
'404_page_not_found' => 'Seite nicht gefunden',
|
||||
'sorry_page_not_found' => 'Entschuldigung. Die Seite, die du angefordert hast, wurde nicht gefunden.',
|
||||
'sorry_page_not_found' => 'Entschuldigung. Die Seite, die Du angefordert hast, wurde nicht gefunden.',
|
||||
'sorry_page_not_found_permission_warning' => 'Wenn du erwartet hast, dass diese Seite existiert, hast du möglicherweise nicht die Berechtigung, sie anzuzeigen.',
|
||||
'image_not_found' => 'Bild nicht gefunden',
|
||||
'image_not_found_subtitle' => 'Sorry. Die Bilddatei, nach der du suchst, konnte nicht gefunden werden.',
|
||||
@@ -99,7 +99,7 @@ return [
|
||||
'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 API-Token angegebene geheime Token ist falsch',
|
||||
'api_incorrect_token_secret' => 'Das für den API-Token angegebene geheimen Token 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',
|
||||
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
*/
|
||||
|
||||
return [
|
||||
'shortcuts' => 'Kürzel',
|
||||
'shortcuts_interface' => 'Oberflächen-Tastaturkürzel',
|
||||
'shortcuts_toggle_desc' => 'Hier kannst du Tastaturkürzel für die Systemoberfläche für Navigation und Aktionen aktivieren oder deaktivieren.',
|
||||
'shortcuts_customize_desc' => 'Unten kannst du alle Tastenkürzel anpassen. Drücke einfach die gewünschte Tastenkombination, nachdem du die Eingabe für eine Tastenkombination ausgewählt hast.',
|
||||
'shortcuts_toggle_label' => 'Tastaturkürzel aktiviert',
|
||||
'shortcuts' => 'Tastenkürzel',
|
||||
'shortcuts_interface' => 'Interface Keyboard Shortcuts',
|
||||
'shortcuts_toggle_desc' => 'Here you can enable or disable keyboard system interface shortcuts, used for navigation and actions.',
|
||||
'shortcuts_customize_desc' => 'You can customize each of the shortcuts below. Just press your desired key combination after selecting the input for a shortcut.',
|
||||
'shortcuts_toggle_label' => 'Keyboard shortcuts enabled',
|
||||
'shortcuts_section_navigation' => 'Navigation',
|
||||
'shortcuts_section_actions' => 'Häufige Aktionen',
|
||||
'shortcuts_section_actions' => 'Gemeinsame Aktionen',
|
||||
'shortcuts_save' => 'Tastenkürzel speichern',
|
||||
'shortcuts_overlay_desc' => 'Hinweis: Wenn Tastenkürzel aktiviert sind, ist ein Hilfefähnchen durch Drücken von "?" verfügbar, welches die verfügbaren Tastenkürzel für Aktionen hervorhebt, die aktuell auf dem Bildschirm sichtbar sind.',
|
||||
'shortcuts_overlay_desc' => 'Hinweis: Wenn Tastenkürzel aktiviert sind, ist ein Hilfsoverlay durch Drücken von "?" verfügbar, welches die verfügbaren Tastenkürzel für Aktionen hervorhebt, die aktuell auf dem Bildschirm sichtbar sind.',
|
||||
'shortcuts_update_success' => 'Tastenkürzel Einstellungen wurden aktualisiert!',
|
||||
];
|
||||
@@ -20,13 +20,13 @@ return [
|
||||
'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 du diese Option aktivierst, können Besucher, die nicht angemeldet sind, auf Inhalte in deiner 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_secure_images' => 'Erhöhte Sicherheit für hochgeladene Bilder aktivieren?',
|
||||
'app_secure_images_toggle' => 'Aktiviere Bild-Upload mit höherer Sicherheit',
|
||||
'app_secure_images_desc' => 'Aus Leistungsgründen sind alle Bilder öffentlich sichtbar. Diese Option fügt zufällige, schwer zu erratene, Zeichenketten zu Bild-URLs hinzu. Stelle sicher, dass Verzeichnisindizes deaktiviert sind, um einen einfachen Zugriff zu verhindern.',
|
||||
'app_secure_images_desc' => 'Aus Leistungsgründen sind alle Bilder öffentlich sichtbar. Diese Option fügt zufällige, schwer zu eratene, Zeichenketten zu Bild-URLs hinzu. Stellen sie sicher, dass Verzeichnisindizes deaktiviert sind, um einen einfachen Zugriff zu verhindern.',
|
||||
'app_default_editor' => 'Standard Seiteneditor',
|
||||
'app_default_editor_desc' => 'Wähle aus, welcher Editor bei der Bearbeitung neuer Seiten standardmäßig verwendet werden soll. Dies kann auf Seitenebene außer Kraft gesetzt werden, sofern die Berechtigungen dies zulassen.',
|
||||
'app_custom_html' => 'Benutzerdefinierter HTML <head> Inhalt',
|
||||
@@ -37,12 +37,12 @@ return [
|
||||
Größere Bilder werden verkleinert.',
|
||||
'app_primary_color' => 'Primäre Anwendungsfarbe',
|
||||
'app_primary_color_desc' => 'Dies sollte ein HEX Wert sein.
|
||||
Wenn du nichts eingibst, wird die Anwendung auf die Standardfarbe zurückgesetzt.',
|
||||
Wenn Du nichts eingibst, wird die Anwendung auf die Standardfarbe zurückgesetzt.',
|
||||
'app_homepage' => 'Startseite der Anwendung',
|
||||
'app_homepage_desc' => 'Wähle eine Seite als Startseite aus, die statt der Standardansicht angezeigt werden soll. Seitenberechtigungen werden für die ausgewählten Seiten ignoriert.',
|
||||
'app_homepage_select' => 'Wähle eine Seite aus',
|
||||
'app_homepage_select' => 'Wählen Sie eine Seite aus',
|
||||
'app_footer_links' => 'Fußzeilen-Links',
|
||||
'app_footer_links_desc' => 'Füge 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. Du kannst 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ßzeilenlink hinzufügen',
|
||||
@@ -70,7 +70,7 @@ Wenn du nichts eingibst, wird die Anwendung auf die Standardfarbe zurückgesetzt
|
||||
'reg_email_confirmation_toggle' => 'Bestätigung per E-Mail erforderlich',
|
||||
'reg_confirm_email_desc' => 'Falls die Einschränkung für Domains genutzt wird, ist die Bestätigung per E-Mail zwingend erforderlich und der untenstehende Wert wird ignoriert.',
|
||||
'reg_confirm_restrict_domain' => 'Registrierung auf bestimmte Domains einschränken',
|
||||
'reg_confirm_restrict_domain_desc' => 'Füge eine durch Komma getrennte Liste von Domains hinzu, auf die die Registrierung eingeschränkt werden soll. Benutzern wird eine E-Mail gesendet, um ihre E-Mail Adresse zu bestätigen, bevor sie diese Anwendung nutzen können.
|
||||
'reg_confirm_restrict_domain_desc' => 'Fügen sie eine durch Komma getrennte Liste von Domains hinzu, auf die die Registrierung eingeschränkt werden soll. Benutzern wird eine E-Mail gesendet, um ihre E-Mail Adresse zu bestätigen, bevor sie diese Anwendung nutzen können.
|
||||
Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung ändern.',
|
||||
'reg_confirm_restrict_domain_placeholder' => 'Keine Einschränkung gesetzt',
|
||||
|
||||
@@ -80,10 +80,10 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung
|
||||
'maint_image_cleanup_desc' => 'Überprüft Seiten- und Versionsinhalte auf ungenutzte und mehrfach vorhandene Bilder. Erstelle vor dem Start ein Backup Deiner Datenbank und Bilder.',
|
||||
'maint_delete_images_only_in_revisions' => 'Lösche auch Bilder, die nur in alten Seitenüberarbeitungen vorhanden sind',
|
||||
'maint_image_cleanup_run' => 'Reinigung starten',
|
||||
'maint_image_cleanup_warning' => ':count eventuell unbenutze Bilder wurden gefunden. Möchtest du diese Bilder löschen?',
|
||||
'maint_image_cleanup_warning' => ':count eventuell unbenutze Bilder wurden gefunden. Möchtest Du diese Bilder löschen?',
|
||||
'maint_image_cleanup_success' => ':count eventuell unbenutze Bilder wurden gefunden und gelöscht.',
|
||||
'maint_image_cleanup_nothing_found' => 'Keine unbenutzen Bilder gefunden. Nichts zu löschen!',
|
||||
'maint_send_test_email' => 'Test E-Mail versenden',
|
||||
'maint_send_test_email' => 'Test Email versenden',
|
||||
'maint_send_test_email_desc' => 'Dies sendet eine Test E-Mail an die in deinem Profil angegebene E-Mail-Adresse.',
|
||||
'maint_send_test_email_run' => 'Sende eine Test E-Mail',
|
||||
'maint_send_test_email_success' => 'E-Mail wurde an :address gesendet',
|
||||
@@ -99,7 +99,7 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung
|
||||
|
||||
// Recycle Bin
|
||||
'recycle_bin' => 'Papierkorb',
|
||||
'recycle_bin_desc' => 'Hier kannst du 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_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',
|
||||
@@ -108,11 +108,11 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung
|
||||
'recycle_bin_restore' => 'Wiederherstellen',
|
||||
'recycle_bin_contents_empty' => 'Der Papierkorb ist derzeit leer',
|
||||
'recycle_bin_empty' => 'Papierkorb leeren',
|
||||
'recycle_bin_empty_confirm' => 'Dies wird alle Einträge im Papierkorb dauerhaft entfernen, einschließlich der Inhalte, die darin enthalten sind. Bist du sicher, dass du den Papierkorb leeren möchtest?',
|
||||
'recycle_bin_destroy_confirm' => 'Diese Aktion wird diesen Eintrag zusammen mit allen unten aufgeführten Untereinträgen dauerhaft aus dem System löschen und du wirst nicht in der Lage sein, diesen Inhalt wiederherzustellen. Bist du sicher, dass du diesen Eintrag endgültig löschen möchtest?',
|
||||
'recycle_bin_empty_confirm' => 'Dies wird alle Einträge im Papierkorb dauerhaft entfernen, einschließlich der Inhalte, die darin enthalten sind. Sind Sie sicher, dass Sie den Papierkorb leeren möchten?',
|
||||
'recycle_bin_destroy_confirm' => 'Diese Aktion wird diesen Eintrag zusammen mit allen unten aufgeführten Untereinträgen dauerhaft aus dem System löschen und Sie werden nicht in der Lage sein, diesen Inhalt wiederherzustellen. Sind Sie sicher, dass Sie diesen Eintrag endgültig löschen möchten?',
|
||||
'recycle_bin_destroy_list' => 'Zu löschende Einträge',
|
||||
'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 seinem ursprünglichen Ort wiederhergestellt. Wenn der ursprüngliche Ort gelöscht wurde und sich nun im Papierkorb befindet, muss auch der übergeordnete Eintrag wiederhergestellt werden.',
|
||||
'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.',
|
||||
@@ -136,18 +136,18 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung
|
||||
// Role Settings
|
||||
'roles' => 'Rollen',
|
||||
'role_user_roles' => 'Benutzer-Rollen',
|
||||
'roles_index_desc' => 'Rollen werden verwendet, um Benutzer zu gruppieren und System-Berechtigungen für ihre Mitglieder zuzuweisen. Wenn ein Benutzer Mitglied mehrerer Rollen ist, stapeln die gewährten Berechtigungen und der Benutzer wird alle Fähigkeiten erben.',
|
||||
'roles_x_users_assigned' => '1 Benutzer zugewiesen|:count Benutzer zugewiesen',
|
||||
'roles_x_permissions_provided' => '1 Berechtigung|:count Berechtigungen',
|
||||
'roles_assigned_users' => 'Zugewiesene Benutzer',
|
||||
'roles_permissions_provided' => 'Genutzte Berechtigungen',
|
||||
'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.',
|
||||
'roles_x_users_assigned' => '1 user assigned|:count users assigned',
|
||||
'roles_x_permissions_provided' => '1 permission|:count permissions',
|
||||
'roles_assigned_users' => 'Assigned Users',
|
||||
'roles_permissions_provided' => 'Provided Permissions',
|
||||
'role_create' => 'Neue Rolle anlegen',
|
||||
'role_create_success' => 'Rolle erfolgreich angelegt',
|
||||
'role_delete' => 'Rolle löschen',
|
||||
'role_delete_confirm' => 'Dies wird die Rolle ":roleName" löschen.',
|
||||
'role_delete_users_assigned' => 'Diese Rolle ist :userCount Benutzern zugeordnet. Du kannst unten eine neue Rolle auswählen, die du diesen Benutzern zuordnen möchtest.',
|
||||
'role_delete_confirm' => 'Du möchtest die Rolle ":roleName" löschen.',
|
||||
'role_delete_users_assigned' => 'Diese Rolle ist :userCount Benutzern zugeordnet. Du kannst unten eine neue Rolle auswählen, die Du diesen Benutzern zuordnen möchtest.',
|
||||
'role_delete_no_migration' => "Den Benutzern keine andere Rolle zuordnen",
|
||||
'role_delete_sure' => 'Bist du sicher, dass du diese Rolle löschen möchtest?',
|
||||
'role_delete_sure' => 'Bist Du sicher, dass Du diese Rolle löschen möchtest?',
|
||||
'role_delete_success' => 'Rolle erfolgreich gelöscht',
|
||||
'role_edit' => 'Rolle bearbeiten',
|
||||
'role_details' => 'Rollendetails',
|
||||
@@ -166,8 +166,8 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung
|
||||
'role_export_content' => 'Inhalt exportieren',
|
||||
'role_editor_change' => 'Seiteneditor ändern',
|
||||
'role_asset' => 'Berechtigungen',
|
||||
'roles_system_warning' => 'Beachte, 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. Weise 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 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.',
|
||||
'role_asset_admins' => 'Administratoren erhalten automatisch Zugriff auf alle Inhalte, aber diese Optionen können Oberflächenoptionen ein- oder ausblenden.',
|
||||
'role_asset_image_view_note' => 'Das bezieht sich auf die Sichtbarkeit innerhalb des Bildmanagers. Der tatsächliche Zugriff auf hochgeladene Bilddateien hängt von der Speicheroption des Systems für Bilder ab.',
|
||||
'role_all' => 'Alle',
|
||||
@@ -180,14 +180,14 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung
|
||||
|
||||
// Users
|
||||
'users' => 'Benutzer',
|
||||
'users_index_desc' => 'Erstelle und Verwalte individuelle Benutzerkonten innerhalb des Systems. Benutzerkonten werden zur Anmeldung und der Zuordnung von Inhalten und Aktivitäten verwendet. Zugriffsberechtigungen sind in erster Linie rollenbasiert, aber der Besitz von Benutzerinhalten kann unter anderem auch Berechtigungen beeinflussen.',
|
||||
'users_index_desc' => 'Create & manage individual user accounts within the system. User accounts are used for login and attribution of content & activity. Access permissions are primarily role-based but user content ownership, among other factors, may also affect permissions & access.',
|
||||
'user_profile' => 'Benutzerprofil',
|
||||
'users_add_new' => 'Benutzer hinzufügen',
|
||||
'users_search' => 'Benutzer suchen',
|
||||
'users_latest_activity' => 'Neueste Aktivitäten',
|
||||
'users_details' => 'Benutzerdetails',
|
||||
'users_details_desc' => 'Lege für diesen Benutzer einen Anzeigenamen und eine E-Mail-Adresse fest. Die E-Mail-Adresse wird bei der Anmeldung verwendet.',
|
||||
'users_details_desc_no_email' => 'Lege für diesen Benutzer einen Anzeigenamen fest, damit andere ihn erkennen können.',
|
||||
'users_details_desc' => 'Legen Sie für diesen Benutzer einen Anzeigenamen und eine E-Mail-Adresse fest. Die E-Mail-Adresse wird bei der Anmeldung verwendet.',
|
||||
'users_details_desc_no_email' => 'Legen Sie für diesen Benutzer einen Anzeigenamen fest, damit andere ihn erkennen können.',
|
||||
'users_role' => 'Benutzerrollen',
|
||||
'users_role_desc' => 'Wählen Sie aus, welchen Rollen dieser Benutzer zugeordnet werden soll. Wenn ein Benutzer mehreren Rollen zugeordnet ist, werden die Berechtigungen dieser Rollen gestapelt und er erhält alle Fähigkeiten der zugewiesenen Rollen.',
|
||||
'users_password' => 'Benutzerpasswort',
|
||||
@@ -196,14 +196,14 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung
|
||||
'users_send_invite_option' => 'Benutzer-Einladungs-E-Mail senden',
|
||||
'users_external_auth_id' => 'Externe Authentifizierungs-ID',
|
||||
'users_external_auth_id_desc' => 'Dies ist die ID, die verwendet wird, um diesen Benutzer bei der Kommunikation mit deinem externen Authentifizierungssystem abzugleichen.',
|
||||
'users_password_warning' => 'Fülle die folgenden Felder nur aus, wenn du dein Passwort ändern möchtest:',
|
||||
'users_password_warning' => 'Fülle die folgenden Felder nur aus, wenn Du Dein Passwort ändern möchtest:',
|
||||
'users_system_public' => 'Dieser Benutzer repräsentiert alle unangemeldeten Benutzer, die diese Seite betrachten. Er kann nicht zum Anmelden benutzt werden, sondern wird automatisch zugeordnet.',
|
||||
'users_delete' => 'Benutzer löschen',
|
||||
'users_delete_named' => 'Benutzer ":userName" löschen',
|
||||
'users_delete_warning' => 'Der Benutzer ":userName" wird aus dem System gelöscht.',
|
||||
'users_delete_confirm' => 'Bist du sicher, dass du diesen Benutzer löschen möchtest?',
|
||||
'users_delete_confirm' => 'Bist Du sicher, dass Du diesen Benutzer löschen möchtest?',
|
||||
'users_migrate_ownership' => 'Besitz migrieren',
|
||||
'users_migrate_ownership_desc' => 'Wähle hier einen Benutzer, wenn du möchtest, dass ein anderer Benutzer der Besitzer aller Einträge wird, die diesem Benutzer derzeit gehören.',
|
||||
'users_migrate_ownership_desc' => 'Wählen Sie hier einen Benutzer, wenn Sie möchten, dass ein anderer Benutzer der Besitzer aller Einträge wird, die diesem Benutzer derzeit gehören.',
|
||||
'users_none_selected' => 'Kein Benutzer ausgewählt',
|
||||
'users_edit' => 'Benutzer bearbeiten',
|
||||
'users_edit_profile' => 'Profil bearbeiten',
|
||||
@@ -218,7 +218,7 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung
|
||||
'users_social_connected' => ':socialAccount-Konto wurde erfolgreich mit dem Profil verknüpft.',
|
||||
'users_social_disconnected' => ':socialAccount-Konto wurde erfolgreich vom Profil gelöst.',
|
||||
'users_api_tokens' => 'API-Token',
|
||||
'users_api_tokens_none' => 'Für diesen Benutzer wurden kein API-Token erstellt',
|
||||
'users_api_tokens_none' => 'Für diesen Benutzer wurden keine API-Token erstellt',
|
||||
'users_api_tokens_create' => 'Token erstellen',
|
||||
'users_api_tokens_expires' => 'Endet',
|
||||
'users_api_tokens_docs' => 'API Dokumentation',
|
||||
@@ -232,7 +232,7 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung
|
||||
'user_api_token_name' => 'Name',
|
||||
'user_api_token_name_desc' => 'Gebe deinem Token einen aussagekräftigen Namen als spätere Erinnerung an seinen Verwendungszweck.',
|
||||
'user_api_token_expiry' => 'Ablaufdatum',
|
||||
'user_api_token_expiry_desc' => 'Lege ein Datum fest, zu dem dieser Token abläuft. Nach diesem Datum funktionieren Anfragen, die mit diesem Token gestellt werden, nicht mehr. Wenn du dieses Feld leer lässt, wird ein Ablaufdatum von 100 Jahren in der Zukunft festgelegt.',
|
||||
'user_api_token_expiry_desc' => 'Lege ein Datum fest, an dem dieser Token abläuft. Nach diesem Datum funktionieren Anfragen, die mit diesem Token gestellt werden, nicht mehr. Wenn du dieses Feld leer lässt, wird ein Ablaufdatum von 100 Jahren in der Zukunft festgelegt.',
|
||||
'user_api_token_create_secret_message' => 'Unmittelbar nach der Erstellung dieses Tokens wird eine "Token ID" & ein "Token Kennwort" generiert und angezeigt. Das Kennwort wird nur ein einziges Mal angezeigt. Stelle also sicher, dass du den Inhalt an einen sicheren Ort kopierst, bevor du fortfährst.',
|
||||
'user_api_token_create_success' => 'API-Token erfolgreich erstellt',
|
||||
'user_api_token_update_success' => 'API-Token erfolgreich aktualisiert',
|
||||
@@ -250,8 +250,8 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung
|
||||
|
||||
// Webhooks
|
||||
'webhooks' => 'Webhooks',
|
||||
'webhooks_index_desc' => 'Webhooks sind eine Möglichkeit, Daten an externe URLs zu senden, wenn bestimmte Aktionen und Ereignisse im System auftreten, was eine ereignisbasierte Integration mit externen Plattformen wie Messaging- oder Benachrichtigungssystemen ermöglicht.',
|
||||
'webhooks_x_trigger_events' => '1 Triggerereignis|:count Triggerereignisse',
|
||||
'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.',
|
||||
'webhooks_x_trigger_events' => '1 trigger event|:count trigger events',
|
||||
'webhooks_create' => 'Neuen Webhook erstellen',
|
||||
'webhooks_none_created' => 'Es wurden noch keine Webhooks erstellt.',
|
||||
'webhooks_edit' => 'Webhook bearbeiten',
|
||||
|
||||
@@ -61,8 +61,8 @@ return [
|
||||
'email_confirm_send_error' => 'Απαιτείται επιβεβαίωση μέσω email, αλλά το σύστημα δεν μπόρεσε να στείλει το email. Επικοινωνήστε με τον διαχειριστή για να βεβαιωθείτε ότι το email έχει ρυθμιστεί σωστά.',
|
||||
'email_confirm_success' => 'Το email σας επιβεβαιώθηκε! Θα πρέπει τώρα να μπορείτε να συνδεθείτε χρησιμοποιώντας αυτήν τη διεύθυνση email.',
|
||||
'email_confirm_resent' => 'Το email επιβεβαίωσης στάλθηκε εκ νέου. Ελέγξτε τα εισερχόμενά σας.',
|
||||
'email_confirm_thanks' => 'Ευχαριστούμε για την επιβεβαίωση!',
|
||||
'email_confirm_thanks_desc' => 'Παρακαλώ περιμένετε λίγο όσο διεκπεραιώνεται η επιβεβαίωσή σας. Εάν δεν ανακατευθυνθείτε μετά από 3 δευτερόλεπτα, πατήστε τον παρακάτω σύνδεσμο "Συνέχεια" για να συνεχίσετε.',
|
||||
'email_confirm_thanks' => 'Thanks for confirming!',
|
||||
'email_confirm_thanks_desc' => 'Please wait a moment while your confirmation is handled. If you are not redirected after 3 seconds press the "Continue" link below to proceed.',
|
||||
|
||||
'email_not_confirmed' => 'Η διεύθυνση email δεν επιβεβαιώθηκε',
|
||||
'email_not_confirmed_text' => 'Η διεύθυνση email σας δεν έχει ακόμη επιβεβαιωθεί.',
|
||||
|
||||
@@ -25,7 +25,7 @@ return [
|
||||
'actions' => 'Ενέργειες',
|
||||
'view' => 'Προβολή',
|
||||
'view_all' => 'Προβολή όλων',
|
||||
'new' => 'Νέο',
|
||||
'new' => 'New',
|
||||
'create' => 'Δημιουργία',
|
||||
'update' => 'Ενημέρωση',
|
||||
'edit' => 'Επεξεργασία',
|
||||
@@ -81,14 +81,14 @@ return [
|
||||
'none' => 'Κανένας',
|
||||
|
||||
// Header
|
||||
'homepage' => 'Αρχική σελίδα',
|
||||
'homepage' => 'Homepage',
|
||||
'header_menu_expand' => 'Αναπτύξτε το Head Menu',
|
||||
'profile_menu' => 'Μενού Προφίλ',
|
||||
'view_profile' => 'Προβολή προφίλ',
|
||||
'edit_profile' => 'Επεξεργασία προφίλ',
|
||||
'dark_mode' => 'Σκουρόχρωμη εμφάνιση',
|
||||
'light_mode' => 'Ανοιχτόχρωμη εμφάνιση',
|
||||
'global_search' => 'Καθολική αναζήτηση',
|
||||
'global_search' => 'Global Search',
|
||||
|
||||
// Layout tabs
|
||||
'tab_info' => 'Πληροφορίες',
|
||||
|
||||
@@ -66,7 +66,7 @@ return [
|
||||
'insert_link_title' => 'Εισαγωγή/Επεξεργασία συνδέσμου',
|
||||
'insert_horizontal_line' => 'Εισαγωγή οριζόντιας γραμμής',
|
||||
'insert_code_block' => 'Εισαγωγή μπλοκ κώδικα',
|
||||
'edit_code_block' => 'Επεξεργασία μπλοκ κώδικα',
|
||||
'edit_code_block' => 'Edit code block',
|
||||
'insert_drawing' => 'Εισαγωγή/Επεξεργασία σχεδίου',
|
||||
'drawing_manager' => 'Διαχειριστής σχεδίασης',
|
||||
'insert_media' => 'Εισαγωγή/Επεξεργασία πολυμέσων',
|
||||
@@ -144,11 +144,11 @@ return [
|
||||
'url' => 'URL',
|
||||
'text_to_display' => 'Κείμενο εμφάνισης',
|
||||
'title' => 'Τίτλος',
|
||||
'open_link' => 'Άνοιγμα συνδέσμου',
|
||||
'open_link_in' => 'Άνοιγμα συνδέσμου σε...',
|
||||
'open_link' => 'Open link',
|
||||
'open_link_in' => 'Open link in...',
|
||||
'open_link_current' => 'Τρέχον παράθυρο',
|
||||
'open_link_new' => 'Νέο παράθυρο',
|
||||
'remove_link' => 'Αφαίρεση συνδέσμου',
|
||||
'remove_link' => 'Remove link',
|
||||
'insert_collapsible' => 'Εισαγωγή πτυσσόμενου μπλοκ',
|
||||
'collapsible_unwrap' => 'Μετατροπή πτυσσόμενου μπλοκ σε παράγραφο',
|
||||
'edit_label' => 'Επεξεργασία ετικέτας',
|
||||
|
||||
@@ -42,15 +42,15 @@ return [
|
||||
|
||||
// Permissions and restrictions
|
||||
'permissions' => 'Δικαιώματα',
|
||||
'permissions_desc' => 'Ορίστε εδώ δικαιώματα για να παρακάμψετε τα προκαθορισμένα δικαιώματα που παρέχονται από τους ρόλους των χρηστών.',
|
||||
'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.',
|
||||
'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.',
|
||||
'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.',
|
||||
'permissions_save' => 'Αποθήκευση Δικαιωμάτων',
|
||||
'permissions_owner' => 'Ιδιοκτήτης / Κάτοχος',
|
||||
'permissions_role_everyone_else' => 'Everyone Else',
|
||||
'permissions_role_everyone_else_desc' => 'Ορίστε δικαιώματα για όλους τους ρόλους που δεν παραβλέπονται συγκεκριμένα.',
|
||||
'permissions_role_override' => 'Παράκαμψη δικαιωμάτων για ρόλο',
|
||||
'permissions_inherit_defaults' => 'Κληρονόμηση προεπιλογών',
|
||||
'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.',
|
||||
'permissions_role_override' => 'Override permissions for role',
|
||||
'permissions_inherit_defaults' => 'Inherit defaults',
|
||||
|
||||
// Search
|
||||
'search_results' => 'Αποτελέσματα αναζήτησης',
|
||||
@@ -224,8 +224,8 @@ return [
|
||||
'pages_md_insert_image' => 'Εισαγωγή Εικόνας',
|
||||
'pages_md_insert_link' => 'Εισαγωγή/Επεξεργασία συνδέσμου',
|
||||
'pages_md_insert_drawing' => 'Εισαγωγή Σχεδίου',
|
||||
'pages_md_show_preview' => 'Εμφάνιση προεπισκόπησης',
|
||||
'pages_md_sync_scroll' => 'Συγχρονισμός προεπισκόπησης',
|
||||
'pages_md_show_preview' => 'Show preview',
|
||||
'pages_md_sync_scroll' => 'Sync preview scroll',
|
||||
'pages_not_in_chapter' => 'Η σελίδα δεν είναι σε κεφάλαιο',
|
||||
'pages_move' => 'Μετακίνηση Σελίδας',
|
||||
'pages_move_success' => 'Η σελίδα μετακινήθηκε στο ":parentName"',
|
||||
@@ -243,7 +243,7 @@ return [
|
||||
'pages_revisions_created_by' => 'Δημιουργήθηκε από',
|
||||
'pages_revisions_date' => 'Ημερομηνία Αναθεώρησης',
|
||||
'pages_revisions_number' => '#',
|
||||
'pages_revisions_sort_number' => 'Αριθμός αναθεώρησης',
|
||||
'pages_revisions_sort_number' => 'Revision Number',
|
||||
'pages_revisions_numbered' => 'Αναθεώρηση #',
|
||||
'pages_revisions_numbered_changes' => 'Αναθεώρηση #:id Αλλαγές',
|
||||
'pages_revisions_editor' => 'Τύπος Επεξεργαστή',
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
*/
|
||||
|
||||
return [
|
||||
'shortcuts' => 'Συντομεύσεις',
|
||||
'shortcuts_interface' => 'Συντομεύσεις Πληκτρολογίου Διεπαφής',
|
||||
'shortcuts_toggle_desc' => 'Εδώ μπορείτε να ενεργοποιήσετε ή να απενεργοποιήσετε τις συντομεύσεις του συστήματος πληκτρολογίου, που χρησιμοποιούνται για την πλοήγηση και τις ενέργειες.',
|
||||
'shortcuts_customize_desc' => 'Μπορείτε να προσαρμόσετε κάθε μία από τις παρακάτω συντομεύσεις. Απλά πατήστε το επιθυμητό συνδυασμό πλήκτρων μετά την επιλογή της εισόδου για μια συντόμευση.',
|
||||
'shortcuts_toggle_label' => 'Ενεργοποίηση συντομεύσεων πληκτρολογίου',
|
||||
'shortcuts_section_navigation' => 'Πλοήγηση',
|
||||
'shortcuts_section_actions' => 'Κοινές ενέργειες',
|
||||
'shortcuts_save' => 'Αποθήκευση Συντομεύσεων',
|
||||
'shortcuts_overlay_desc' => 'Σημείωση: Όταν οι συντομεύσεις είναι ενεργοποιημένες μια βοηθητική επικάλυψη είναι διαθέσιμη πατώντας "?" που θα τονίσει τις διαθέσιμες συντομεύσεις για ενέργειες που είναι ορατές στην οθόνη.',
|
||||
'shortcuts_update_success' => 'Οι προτιμήσεις σας αποθηκεύτηκαν!',
|
||||
'shortcuts' => 'Shortcuts',
|
||||
'shortcuts_interface' => 'Interface Keyboard Shortcuts',
|
||||
'shortcuts_toggle_desc' => 'Here you can enable or disable keyboard system interface shortcuts, used for navigation and actions.',
|
||||
'shortcuts_customize_desc' => 'You can customize each of the shortcuts below. Just press your desired key combination after selecting the input for a shortcut.',
|
||||
'shortcuts_toggle_label' => 'Keyboard shortcuts enabled',
|
||||
'shortcuts_section_navigation' => 'Navigation',
|
||||
'shortcuts_section_actions' => 'Common Actions',
|
||||
'shortcuts_save' => 'Save Shortcuts',
|
||||
'shortcuts_overlay_desc' => 'Note: When shortcuts are enabled a helper overlay is available via pressing "?" which will highlight the available shortcuts for actions currently visible on the screen.',
|
||||
'shortcuts_update_success' => 'Shortcut preferences have been updated!',
|
||||
];
|
||||
@@ -136,8 +136,8 @@ return [
|
||||
'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.',
|
||||
'roles_x_users_assigned' => '1 user assigned|:count users assigned',
|
||||
'roles_x_permissions_provided' => '1 permission|:count permissions',
|
||||
'roles_assigned_users' => 'Εκχωρημένοι χρήστες',
|
||||
'roles_permissions_provided' => 'Παρεχόμενα Δικαιώματα',
|
||||
'roles_assigned_users' => 'Assigned Users',
|
||||
'roles_permissions_provided' => 'Provided Permissions',
|
||||
'role_create' => 'Δημιουργία νέου ρόλου',
|
||||
'role_create_success' => 'Ο Ρόλος δημιουργήθηκε με επιτυχία',
|
||||
'role_delete' => 'Διαγραφή Ρόλου',
|
||||
|
||||
@@ -18,6 +18,7 @@ return [
|
||||
'name' => 'Name',
|
||||
'description' => 'Description',
|
||||
'role' => 'Role',
|
||||
'user' => 'User',
|
||||
'cover_image' => 'Cover image',
|
||||
'cover_image_description' => 'This image should be approx 440x250px.',
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ return [
|
||||
'permissions_role_everyone_else' => 'Everyone Else',
|
||||
'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.',
|
||||
'permissions_role_override' => 'Override permissions for role',
|
||||
'permissions_user_override' => 'Override permissions for user',
|
||||
'permissions_inherit_defaults' => 'Inherit defaults',
|
||||
|
||||
// Search
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
*/
|
||||
|
||||
return [
|
||||
'shortcuts' => 'Accesos directos',
|
||||
'shortcuts_interface' => 'Accesos directos de la interfaz',
|
||||
'shortcuts_toggle_desc' => 'Aquí puede activar o desactivar los accesos directos de la interfaz, utilizados para la navegación y las acciones.',
|
||||
'shortcuts' => 'Accesos rápidos',
|
||||
'shortcuts_interface' => 'Atajos del Teclado de la Interfaz',
|
||||
'shortcuts_toggle_desc' => 'Aquí puede activar o desactivar los accesos rápidos de la interfaz, utilizados para la navegación y las acciones.',
|
||||
'shortcuts_customize_desc' => 'Puede personalizar cada uno de los accesos directos a continuación. Simplemente pulse la combinación de teclas deseada después de seleccionar la entrada para un acceso directo.',
|
||||
'shortcuts_toggle_label' => 'Accesos directos habilitados',
|
||||
'shortcuts_section_navigation' => 'Navegación',
|
||||
'shortcuts_section_actions' => 'Acciones comunes',
|
||||
'shortcuts_save' => 'Guardar accesos directos',
|
||||
'shortcuts_save' => 'Guardar atajos',
|
||||
'shortcuts_overlay_desc' => 'Nota: Cuando se activan los accesos directos se puede mostrar la ayuda presionando la tecla "?" que resaltará los accesos rápidos disponibles para las acciones actualmente visibles en la pantalla.',
|
||||
'shortcuts_update_success' => '¡Las preferencias de accesos directos han sido actualizadas!',
|
||||
'shortcuts_update_success' => '¡Las preferencias de accesos rápidos han sido actualizadas!',
|
||||
];
|
||||
@@ -224,8 +224,8 @@ return [
|
||||
'pages_md_insert_image' => 'Lisa pilt',
|
||||
'pages_md_insert_link' => 'Lisa viide',
|
||||
'pages_md_insert_drawing' => 'Lisa joonis',
|
||||
'pages_md_show_preview' => 'Näita eelvaadet',
|
||||
'pages_md_sync_scroll' => 'Sünkrooni eelvaate kerimine',
|
||||
'pages_md_show_preview' => 'Show preview',
|
||||
'pages_md_sync_scroll' => 'Sync preview scroll',
|
||||
'pages_not_in_chapter' => 'Leht ei kuulu peatüki alla',
|
||||
'pages_move' => 'Liiguta leht',
|
||||
'pages_move_success' => 'Leht liigutatud ":parentName" alla',
|
||||
|
||||
@@ -28,8 +28,8 @@ return [
|
||||
// Books
|
||||
'book_create' => 'liburua sortuta',
|
||||
'book_create_notification' => 'Liburua ongi sortu da',
|
||||
'book_create_from_chapter' => 'bihurtu kapitulua liburu',
|
||||
'book_create_from_chapter_notification' => 'Kapitulua ongi bilakatu da liburu',
|
||||
'book_create_from_chapter' => 'converted chapter to book',
|
||||
'book_create_from_chapter_notification' => 'Chapter successfully converted to a book',
|
||||
'book_update' => 'liburua eguneratuta',
|
||||
'book_update_notification' => 'Liburua egoki eguneratua',
|
||||
'book_delete' => 'liburua ezabatua',
|
||||
@@ -38,14 +38,14 @@ return [
|
||||
'book_sort_notification' => 'Liburua ongi bersailaktu da',
|
||||
|
||||
// Bookshelves
|
||||
'bookshelf_create' => 'apalategia sortuta',
|
||||
'bookshelf_create_notification' => 'Apalategia egoki sortuta',
|
||||
'bookshelf_create_from_book' => 'liburua apalategi bihurtuta',
|
||||
'bookshelf_create_from_book_notification' => 'Kapitulua ongi bilakatu da liburu',
|
||||
'bookshelf_update' => 'apalategia eguneratuta',
|
||||
'bookshelf_update_notification' => 'Erabiltzailea egoki eguneratua',
|
||||
'bookshelf_delete' => 'apalategia ezabatua',
|
||||
'bookshelf_delete_notification' => 'Apalategia egoki ezabatua',
|
||||
'bookshelf_create' => 'created shelf',
|
||||
'bookshelf_create_notification' => 'Shelf successfully created',
|
||||
'bookshelf_create_from_book' => 'converted book to shelf',
|
||||
'bookshelf_create_from_book_notification' => 'Book successfully converted to a shelf',
|
||||
'bookshelf_update' => 'updated shelf',
|
||||
'bookshelf_update_notification' => 'Shelf successfully updated',
|
||||
'bookshelf_delete' => 'deleted shelf',
|
||||
'bookshelf_delete_notification' => 'Shelf successfully deleted',
|
||||
|
||||
// Favourites
|
||||
'favourite_add_notification' => '":name" zure gogoetara gehitua izan da',
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
*/
|
||||
|
||||
return [
|
||||
'shortcuts' => 'Lastertekla',
|
||||
'shortcuts_interface' => 'Teklatuko lasterbideak ikusi',
|
||||
'shortcuts_toggle_desc' => 'Hemen, nabigaziorako eta ekintzetarako erabiltzen diren teklatu-sistemako lasterbideak gaitu edo desgaitu daitezke.',
|
||||
'shortcuts_customize_desc' => 'Beheko lasterbide bakoitza pertsonalizatu dezakezu. Sakatu nahi duzun tekla konbinazioa lasterbide baterako sarrera aukeratu ondoren.',
|
||||
'shortcuts_toggle_label' => 'Teklatu-lasterbideak aktibatuta',
|
||||
'shortcuts_section_navigation' => 'Nabigazioa',
|
||||
'shortcuts_section_actions' => 'Ohiko ekintzak',
|
||||
'shortcuts_save' => 'Gorde lasterbideak',
|
||||
'shortcuts_overlay_desc' => 'Oharra: Lasterbideak gaituta daudenean, "?" sakagailuaren bidez laguntzaileen gainjartze bat egongo da, eta horrek pantailan gaur egun ikus daitezkeen ekintzetarako dauden lasterbideak nabarmenduko ditu.',
|
||||
'shortcuts_update_success' => 'Zure lehentasunak gorde dira!',
|
||||
'shortcuts' => 'Shortcuts',
|
||||
'shortcuts_interface' => 'Interface Keyboard Shortcuts',
|
||||
'shortcuts_toggle_desc' => 'Here you can enable or disable keyboard system interface shortcuts, used for navigation and actions.',
|
||||
'shortcuts_customize_desc' => 'You can customize each of the shortcuts below. Just press your desired key combination after selecting the input for a shortcut.',
|
||||
'shortcuts_toggle_label' => 'Keyboard shortcuts enabled',
|
||||
'shortcuts_section_navigation' => 'Navigation',
|
||||
'shortcuts_section_actions' => 'Common Actions',
|
||||
'shortcuts_save' => 'Save Shortcuts',
|
||||
'shortcuts_overlay_desc' => 'Note: When shortcuts are enabled a helper overlay is available via pressing "?" which will highlight the available shortcuts for actions currently visible on the screen.',
|
||||
'shortcuts_update_success' => 'Shortcut preferences have been updated!',
|
||||
];
|
||||
@@ -74,7 +74,7 @@ return [
|
||||
'search_date_options' => 'گزینه های تاریخ',
|
||||
'search_updated_before' => 'قبلا به روز شده',
|
||||
'search_updated_after' => 'پس از به روز رسانی',
|
||||
'search_created_before' => 'ایجاد شده قبل از',
|
||||
'search_created_before' => 'قبلا ایجاد شده است',
|
||||
'search_created_after' => 'ایجاد شده پس از',
|
||||
'search_set_date' => 'تنظیم تاریخ',
|
||||
'search_update' => 'جستجو را به روز کنید',
|
||||
|
||||
@@ -113,7 +113,7 @@ return [
|
||||
'paste_row_after' => 'Paste row after',
|
||||
'row_type' => 'Row type',
|
||||
'row_type_header' => 'Header',
|
||||
'row_type_body' => 'תוכן',
|
||||
'row_type_body' => 'Body',
|
||||
'row_type_footer' => 'Footer',
|
||||
'alignment' => 'Alignment',
|
||||
'cut_column' => 'Cut column',
|
||||
|
||||
@@ -224,8 +224,8 @@ return [
|
||||
'pages_md_insert_image' => 'Inserisci Immagina',
|
||||
'pages_md_insert_link' => 'Inserisci Link Entità',
|
||||
'pages_md_insert_drawing' => 'Inserisci Disegno',
|
||||
'pages_md_show_preview' => 'Visualizza anteprima',
|
||||
'pages_md_sync_scroll' => 'Sincronizza scorrimento anteprima',
|
||||
'pages_md_show_preview' => 'Show preview',
|
||||
'pages_md_sync_scroll' => 'Sync preview scroll',
|
||||
'pages_not_in_chapter' => 'La pagina non è in un capitolo',
|
||||
'pages_move' => 'Muovi Pagina',
|
||||
'pages_move_success' => 'Pagina mossa in ":parentName"',
|
||||
|
||||
@@ -61,8 +61,8 @@ return [
|
||||
'email_confirm_send_error' => 'Eメールの確認が必要でしたが、システム上でEメールの送信ができませんでした。管理者に連絡し、Eメールが正しく設定されていることを確認してください。',
|
||||
'email_confirm_success' => 'メールアドレスが確認されました!このメールアドレスでログインできるようになりました。',
|
||||
'email_confirm_resent' => '確認メールを再送信しました。受信トレイを確認してください。',
|
||||
'email_confirm_thanks' => '確認いただきありがとうございます!',
|
||||
'email_confirm_thanks_desc' => '確認のため、しばらくお待ちください。3秒後にリダイレクトされない場合は、下の「続ける」リンクを押して次に進んでください。',
|
||||
'email_confirm_thanks' => 'Thanks for confirming!',
|
||||
'email_confirm_thanks_desc' => 'Please wait a moment while your confirmation is handled. If you are not redirected after 3 seconds press the "Continue" link below to proceed.',
|
||||
|
||||
'email_not_confirmed' => 'Eメールアドレスが確認できていません',
|
||||
'email_not_confirmed_text' => 'Eメールアドレスの確認が完了していません。',
|
||||
|
||||
@@ -25,7 +25,7 @@ return [
|
||||
'actions' => '実行',
|
||||
'view' => '表示',
|
||||
'view_all' => 'すべて表示',
|
||||
'new' => '新規作成',
|
||||
'new' => 'New',
|
||||
'create' => '作成',
|
||||
'update' => '更新',
|
||||
'edit' => '編集',
|
||||
@@ -40,7 +40,7 @@ return [
|
||||
'reset' => 'リセット',
|
||||
'remove' => '削除',
|
||||
'add' => '追加',
|
||||
'configure' => '設定',
|
||||
'configure' => 'Configure',
|
||||
'fullscreen' => '全画面',
|
||||
'favourite' => 'お気に入り',
|
||||
'unfavourite' => 'お気に入りから削除',
|
||||
@@ -81,14 +81,14 @@ return [
|
||||
'none' => 'なし',
|
||||
|
||||
// Header
|
||||
'homepage' => 'ホームページ',
|
||||
'homepage' => 'Homepage',
|
||||
'header_menu_expand' => 'ヘッダーメニューを展開',
|
||||
'profile_menu' => 'プロフィールメニュー',
|
||||
'view_profile' => 'プロフィール表示',
|
||||
'edit_profile' => 'プロフィール編集',
|
||||
'dark_mode' => 'ダークモード',
|
||||
'light_mode' => 'ライトモード',
|
||||
'global_search' => 'グローバル検索',
|
||||
'global_search' => 'Global Search',
|
||||
|
||||
// Layout tabs
|
||||
'tab_info' => '情報',
|
||||
|
||||
@@ -66,7 +66,7 @@ return [
|
||||
'insert_link_title' => 'リンクの挿入・編集',
|
||||
'insert_horizontal_line' => '水平線を挿入',
|
||||
'insert_code_block' => 'コードブロックを挿入',
|
||||
'edit_code_block' => 'コードブロックを編集',
|
||||
'edit_code_block' => 'Edit code block',
|
||||
'insert_drawing' => '描画を挿入・編集',
|
||||
'drawing_manager' => '描画マネージャー',
|
||||
'insert_media' => 'メディアの挿入・編集',
|
||||
@@ -144,11 +144,11 @@ return [
|
||||
'url' => 'リンク先URL',
|
||||
'text_to_display' => 'リンク元テキスト',
|
||||
'title' => 'タイトル',
|
||||
'open_link' => 'リンクを開く',
|
||||
'open_link_in' => 'リンク先の表示場所',
|
||||
'open_link' => 'Open link',
|
||||
'open_link_in' => 'Open link in...',
|
||||
'open_link_current' => '同じウィンドウ',
|
||||
'open_link_new' => '新規ウィンドウ',
|
||||
'remove_link' => 'リンクを削除',
|
||||
'remove_link' => 'Remove link',
|
||||
'insert_collapsible' => '折りたたみブロックを追加',
|
||||
'collapsible_unwrap' => 'ブロックの解除',
|
||||
'edit_label' => 'ラベルを編集',
|
||||
|
||||
@@ -42,15 +42,15 @@ return [
|
||||
|
||||
// Permissions and restrictions
|
||||
'permissions' => '権限',
|
||||
'permissions_desc' => 'ユーザーの役割によって提供されるデフォルトの権限を上書きするため、ここで権限を設定します。',
|
||||
'permissions_book_cascade' => 'ブックに設定された権限は、子チャプターや子ページに独自の権限が定義されていない限り、自動的に子チャプターや子ページに継承されます。',
|
||||
'permissions_chapter_cascade' => 'チャプターに設定された権限は、子ページに独自の権限が定義されていない限り、自動的に子ページに継承されます。',
|
||||
'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.',
|
||||
'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.',
|
||||
'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.',
|
||||
'permissions_save' => '権限を保存',
|
||||
'permissions_owner' => '所有者',
|
||||
'permissions_role_everyone_else' => 'その他の全員',
|
||||
'permissions_role_everyone_else_desc' => '明示的に上書きされていないすべての役割の権限を設定します。',
|
||||
'permissions_role_override' => '権限を上書きする役割',
|
||||
'permissions_inherit_defaults' => 'デフォルトを継承',
|
||||
'permissions_role_everyone_else' => 'Everyone Else',
|
||||
'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.',
|
||||
'permissions_role_override' => 'Override permissions for role',
|
||||
'permissions_inherit_defaults' => 'Inherit defaults',
|
||||
|
||||
// Search
|
||||
'search_results' => '検索結果',
|
||||
@@ -224,8 +224,8 @@ return [
|
||||
'pages_md_insert_image' => '画像を挿入',
|
||||
'pages_md_insert_link' => 'エンティティへのリンクを挿入',
|
||||
'pages_md_insert_drawing' => '描画を追加',
|
||||
'pages_md_show_preview' => 'プレビューを表示',
|
||||
'pages_md_sync_scroll' => 'プレビューとスクロールを同期',
|
||||
'pages_md_show_preview' => 'Show preview',
|
||||
'pages_md_sync_scroll' => 'Sync preview scroll',
|
||||
'pages_not_in_chapter' => 'チャプターが設定されていません',
|
||||
'pages_move' => 'ページを移動',
|
||||
'pages_move_success' => 'ページを ":parentName" へ移動しました',
|
||||
@@ -236,14 +236,14 @@ return [
|
||||
'pages_permissions_success' => 'ページの権限を更新しました',
|
||||
'pages_revision' => '編集履歴',
|
||||
'pages_revisions' => '編集履歴',
|
||||
'pages_revisions_desc' => '以下はこのページの過去の全リビジョンです。権限があれば、古いバージョンのページの見返しや比較、復元ができます。システムの設定によっては、古いリビジョンが自動削除されることがあるため、このページの全履歴がここに反映されないことがあります。',
|
||||
'pages_revisions_desc' => 'Listed below are all the past revisions of this page. You can look back upon, compare, and restore old page versions if permissions allow. The full history of the page may not be fully reflected here since, depending on system configuration, old revisions could be auto-deleted.',
|
||||
'pages_revisions_named' => ':pageName のリビジョン',
|
||||
'pages_revision_named' => ':pageName のリビジョン',
|
||||
'pages_revision_restored_from' => '#:id :summary から復元',
|
||||
'pages_revisions_created_by' => '作成者',
|
||||
'pages_revisions_date' => '日付',
|
||||
'pages_revisions_number' => '#',
|
||||
'pages_revisions_sort_number' => 'リビジョン番号',
|
||||
'pages_revisions_sort_number' => 'Revision Number',
|
||||
'pages_revisions_numbered' => 'リビジョン #:id',
|
||||
'pages_revisions_numbered_changes' => 'リビジョン #:id の変更',
|
||||
'pages_revisions_editor' => 'エディタの種類',
|
||||
@@ -280,7 +280,7 @@ return [
|
||||
'shelf_tags' => '本棚のタグ',
|
||||
'tag' => 'タグ',
|
||||
'tags' => 'タグ',
|
||||
'tags_index_desc' => 'システム内のコンテンツにタグを適用して柔軟なカテゴリ分けを行うことができます。タグはキーと値の両方を持つことができ、値は任意です。タグを適用すると、タグの名前と値を使ってコンテンツを検索することができます。',
|
||||
'tags_index_desc' => 'Tags can be applied to content within the system to apply a flexible form of categorization. Tags can have both a key and value, with the value being optional. Once applied, content can then be queried using the tag name and value.',
|
||||
'tag_name' => 'タグの名前',
|
||||
'tag_value' => '内容 (オプション)',
|
||||
'tags_explain' => "タグを設定すると、コンテンツの管理が容易になります。\nより高度な管理をしたい場合、タグに内容を設定できます。",
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
*/
|
||||
|
||||
return [
|
||||
'shortcuts' => 'ショートカット',
|
||||
'shortcuts_interface' => 'インターフェイスのキーボードショートカット',
|
||||
'shortcuts_toggle_desc' => 'ここでは、ナビゲーションやアクションに使用されるキーボードシステムインターフェイスのショートカットを有効または無効にすることができます。',
|
||||
'shortcuts_customize_desc' => '以下の各ショートカットをカスタマイズできます。ショートカットの入力を選択した後、希望のキーの組み合わせを押してください。',
|
||||
'shortcuts_toggle_label' => 'キーボードショートカットを有効にする',
|
||||
'shortcuts_section_navigation' => 'ナビゲーション',
|
||||
'shortcuts_section_actions' => '共通のアクション',
|
||||
'shortcuts_save' => 'ショートカットを保存',
|
||||
'shortcuts_overlay_desc' => '注:ショートカットが有効な場合はヘルパーオーバーレイが利用できます。「?」を押すと現在画面に表示されているアクションで利用可能なショートカットをハイライト表示します。',
|
||||
'shortcuts_update_success' => 'ショートカットの設定が更新されました!',
|
||||
'shortcuts' => 'Shortcuts',
|
||||
'shortcuts_interface' => 'Interface Keyboard Shortcuts',
|
||||
'shortcuts_toggle_desc' => 'Here you can enable or disable keyboard system interface shortcuts, used for navigation and actions.',
|
||||
'shortcuts_customize_desc' => 'You can customize each of the shortcuts below. Just press your desired key combination after selecting the input for a shortcut.',
|
||||
'shortcuts_toggle_label' => 'Keyboard shortcuts enabled',
|
||||
'shortcuts_section_navigation' => 'Navigation',
|
||||
'shortcuts_section_actions' => 'Common Actions',
|
||||
'shortcuts_save' => 'Save Shortcuts',
|
||||
'shortcuts_overlay_desc' => 'Note: When shortcuts are enabled a helper overlay is available via pressing "?" which will highlight the available shortcuts for actions currently visible on the screen.',
|
||||
'shortcuts_update_success' => 'Shortcut preferences have been updated!',
|
||||
];
|
||||
@@ -133,9 +133,9 @@ return [
|
||||
// Role Settings
|
||||
'roles' => '役割',
|
||||
'role_user_roles' => '役割',
|
||||
'roles_index_desc' => '役割は、ユーザーをグループ化しメンバーにシステム権限を与えるために使用されます。ユーザーが複数の役割のメンバーである場合、与えられた権限は積み重なり、ユーザーはすべての能力を継承します。',
|
||||
'roles_x_users_assigned' => '1人のユーザーに割り当て|:count人のユーザーに割り当て',
|
||||
'roles_x_permissions_provided' => '1件の権限|:count件の権限',
|
||||
'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.',
|
||||
'roles_x_users_assigned' => '1 user assigned|:count users assigned',
|
||||
'roles_x_permissions_provided' => '1 permission|:count permissions',
|
||||
'roles_assigned_users' => 'Assigned Users',
|
||||
'roles_permissions_provided' => 'Provided Permissions',
|
||||
'role_create' => '役割を作成',
|
||||
@@ -177,7 +177,7 @@ return [
|
||||
|
||||
// Users
|
||||
'users' => 'ユーザー',
|
||||
'users_index_desc' => 'システム内で個々のユーザーアカウントを作成し、管理します。ユーザーアカウントは、ログインおよびコンテンツとアクティビティの帰属のために使用されます。アクセス許可は主に役割ベースですが、ユーザーコンテンツの所有権やその他の要因も、許可とアクセスに影響する場合があります。',
|
||||
'users_index_desc' => 'Create & manage individual user accounts within the system. User accounts are used for login and attribution of content & activity. Access permissions are primarily role-based but user content ownership, among other factors, may also affect permissions & access.',
|
||||
'user_profile' => 'ユーザプロフィール',
|
||||
'users_add_new' => 'ユーザーを追加',
|
||||
'users_search' => 'ユーザー検索',
|
||||
@@ -247,8 +247,8 @@ return [
|
||||
|
||||
// Webhooks
|
||||
'webhooks' => 'Webhook',
|
||||
'webhooks_index_desc' => 'Webhookは、システム内で特定のアクションやイベントが発生したときに外部URLにデータを送信する方法で、メッセージングシステムや通知システムなどの外部プラットフォームとのイベントベースの統合を可能にします。',
|
||||
'webhooks_x_trigger_events' => '1個のトリガーイベント|:count個のトリガーイベント',
|
||||
'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.',
|
||||
'webhooks_x_trigger_events' => '1 trigger event|:count trigger events',
|
||||
'webhooks_create' => 'Webhookを作成',
|
||||
'webhooks_none_created' => 'Webhookはまだ作成されていません。',
|
||||
'webhooks_edit' => 'Webhookを編集',
|
||||
|
||||
@@ -61,8 +61,8 @@ return [
|
||||
'email_confirm_send_error' => 'Wymagane jest potwierdzenie hasła, lecz wiadomość nie mogła zostać wysłana. Skontaktuj się z administratorem w celu upewnienia się, że skrzynka została skonfigurowana prawidłowo.',
|
||||
'email_confirm_success' => 'Twój e-mail został potwierdzony! Powinieneś teraz mieć możliwość zalogowania się za pomocą tego adresu e-mail.',
|
||||
'email_confirm_resent' => 'E-mail z potwierdzeniem został wysłany ponownie, sprawdź swoją skrzynkę odbiorczą.',
|
||||
'email_confirm_thanks' => 'Dzięki za potwierdzenie!',
|
||||
'email_confirm_thanks_desc' => 'Poczekaj chwilę, Twoje potwierdzenie jest obsługiwane. Jeśli nie zostaniesz przekierowany po 3 sekundach, naciśnij poniższy link "Kontynuuj", aby kontynuować.',
|
||||
'email_confirm_thanks' => 'Thanks for confirming!',
|
||||
'email_confirm_thanks_desc' => 'Please wait a moment while your confirmation is handled. If you are not redirected after 3 seconds press the "Continue" link below to proceed.',
|
||||
|
||||
'email_not_confirmed' => 'Adres e-mail nie został potwierdzony',
|
||||
'email_not_confirmed_text' => 'Twój adres e-mail nie został jeszcze potwierdzony.',
|
||||
|
||||
@@ -224,8 +224,8 @@ return [
|
||||
'pages_md_insert_image' => 'Wstaw obrazek',
|
||||
'pages_md_insert_link' => 'Wstaw łącze do obiektu',
|
||||
'pages_md_insert_drawing' => 'Wstaw rysunek',
|
||||
'pages_md_show_preview' => 'Pokaż podgląd',
|
||||
'pages_md_sync_scroll' => 'Synchronizuj przewijanie podglądu',
|
||||
'pages_md_show_preview' => 'Show preview',
|
||||
'pages_md_sync_scroll' => 'Sync preview scroll',
|
||||
'pages_not_in_chapter' => 'Strona nie została umieszczona w rozdziale',
|
||||
'pages_move' => 'Przenieś stronę',
|
||||
'pages_move_success' => 'Strona przeniesiona do ":parentName"',
|
||||
|
||||
@@ -224,8 +224,8 @@ return [
|
||||
'pages_md_insert_image' => 'Inserir Imagem',
|
||||
'pages_md_insert_link' => 'Inserir Link para Entidade',
|
||||
'pages_md_insert_drawing' => 'Inserir Desenho',
|
||||
'pages_md_show_preview' => 'Mostrar pré-visualização',
|
||||
'pages_md_sync_scroll' => 'Sincronizar pré-visualização',
|
||||
'pages_md_show_preview' => 'Show preview',
|
||||
'pages_md_sync_scroll' => 'Sync preview scroll',
|
||||
'pages_not_in_chapter' => 'Página não está dentro de um capítulo',
|
||||
'pages_move' => 'Mover Página',
|
||||
'pages_move_success' => 'Pagina movida para ":parentName"',
|
||||
|
||||
@@ -24,7 +24,7 @@ return [
|
||||
'password_hint' => '必须至少有 8 个字符',
|
||||
'forgot_password' => '忘记密码?',
|
||||
'remember_me' => '记住我',
|
||||
'ldap_email_hint' => '请输入用于此账户的电子邮件。',
|
||||
'ldap_email_hint' => '请输入用于此帐户的电子邮件。',
|
||||
'create_account' => '创建账户',
|
||||
'already_have_account' => '已经有账号了?',
|
||||
'dont_have_account' => '您还没有账号吗?',
|
||||
@@ -50,7 +50,7 @@ return [
|
||||
'reset_password_sent' => '重置密码的链接将通过您的电子邮箱发送:email。',
|
||||
'reset_password_success' => '您的密码已成功重置。',
|
||||
'email_reset_subject' => '重置您的:appName密码',
|
||||
'email_reset_text' => '您收到此电子邮件是因为我们收到了您的账户的密码重置请求。',
|
||||
'email_reset_text' => '您收到此电子邮件是因为我们收到了您的帐户的密码重置请求。',
|
||||
'email_reset_not_requested' => '如果您没有要求重置密码,则不需要采取进一步的操作。',
|
||||
|
||||
// Email Confirmation
|
||||
@@ -61,8 +61,8 @@ return [
|
||||
'email_confirm_send_error' => '需要Email验证,但系统无法发送电子邮件,请联系网站管理员。',
|
||||
'email_confirm_success' => '您已成功验证电子邮件地址!您现在可以使用此电子邮件地址登录。',
|
||||
'email_confirm_resent' => '验证邮件已重新发送,请检查收件箱。',
|
||||
'email_confirm_thanks' => '感谢您的确认!',
|
||||
'email_confirm_thanks_desc' => '请稍等,您的确认正在处理。如果您在3秒后未被重定向,请按下面的“继续“链接继续。',
|
||||
'email_confirm_thanks' => 'Thanks for confirming!',
|
||||
'email_confirm_thanks_desc' => 'Please wait a moment while your confirmation is handled. If you are not redirected after 3 seconds press the "Continue" link below to proceed.',
|
||||
|
||||
'email_not_confirmed' => 'Email地址未验证',
|
||||
'email_not_confirmed_text' => '您的电子邮件地址尚未确认。',
|
||||
@@ -72,11 +72,11 @@ return [
|
||||
|
||||
// User Invite
|
||||
'user_invite_email_subject' => '您已受邀加入 :appName!',
|
||||
'user_invite_email_greeting' => ':appName 已为您创建了一个账户。',
|
||||
'user_invite_email_text' => '点击下面的按钮以设置账户密码并获得访问权限:',
|
||||
'user_invite_email_action' => '设置账号密码',
|
||||
'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_text' => '要完成您的帐户并获得访问权限,您需要设置一个密码,该密码将在以后访问时用于登录 :appName。',
|
||||
'user_invite_page_confirm_button' => '确认密码',
|
||||
'user_invite_success_login' => '密码已设置,您现在可以使用您设置的密码登录 :appName!',
|
||||
|
||||
@@ -87,7 +87,7 @@ return [
|
||||
'mfa_setup_reconfigure' => '重新配置',
|
||||
'mfa_setup_remove_confirmation' => '您确定想要移除多重身份认证吗?',
|
||||
'mfa_setup_action' => '设置',
|
||||
'mfa_backup_codes_usage_limit_warning' => '您剩余的备用认证码少于 5 个,请在用完认证码之前生成并保存新的认证码,以防止您的账户被锁定。',
|
||||
'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' => '备用认证码',
|
||||
|
||||
@@ -25,7 +25,7 @@ return [
|
||||
'actions' => '操作',
|
||||
'view' => '浏览',
|
||||
'view_all' => '查看全部',
|
||||
'new' => '新',
|
||||
'new' => 'New',
|
||||
'create' => '创建',
|
||||
'update' => '更新',
|
||||
'edit' => '编辑',
|
||||
@@ -81,14 +81,14 @@ return [
|
||||
'none' => '无',
|
||||
|
||||
// Header
|
||||
'homepage' => '主页',
|
||||
'homepage' => 'Homepage',
|
||||
'header_menu_expand' => '展开标头菜单',
|
||||
'profile_menu' => '个人资料',
|
||||
'view_profile' => '查看个人资料',
|
||||
'edit_profile' => '编辑个人资料',
|
||||
'dark_mode' => '夜间模式',
|
||||
'light_mode' => '日间模式',
|
||||
'global_search' => '全局搜索',
|
||||
'global_search' => 'Global Search',
|
||||
|
||||
// Layout tabs
|
||||
'tab_info' => '信息',
|
||||
|
||||
@@ -144,11 +144,11 @@ return [
|
||||
'url' => '网址',
|
||||
'text_to_display' => '要显示的文本',
|
||||
'title' => '标题',
|
||||
'open_link' => '打开链接',
|
||||
'open_link_in' => '打开链接于……',
|
||||
'open_link' => 'Open link',
|
||||
'open_link_in' => 'Open link in...',
|
||||
'open_link_current' => '覆盖当前窗口',
|
||||
'open_link_new' => '新建窗口',
|
||||
'remove_link' => '移除链接',
|
||||
'remove_link' => 'Remove link',
|
||||
'insert_collapsible' => '插入可折叠块',
|
||||
'collapsible_unwrap' => '展开',
|
||||
'edit_label' => '编辑标签',
|
||||
|
||||
@@ -50,7 +50,7 @@ return [
|
||||
'permissions_role_everyone_else' => '其他所有人',
|
||||
'permissions_role_everyone_else_desc' => '为所有未被特别覆盖的角色设置权限。',
|
||||
'permissions_role_override' => '覆盖角色权限',
|
||||
'permissions_inherit_defaults' => '继承默认值',
|
||||
'permissions_inherit_defaults' => 'Inherit defaults',
|
||||
|
||||
// Search
|
||||
'search_results' => '搜索结果',
|
||||
@@ -224,8 +224,8 @@ return [
|
||||
'pages_md_insert_image' => '插入图片',
|
||||
'pages_md_insert_link' => '插入项目链接',
|
||||
'pages_md_insert_drawing' => '插入图表',
|
||||
'pages_md_show_preview' => '显示预览',
|
||||
'pages_md_sync_scroll' => '同步预览滚动',
|
||||
'pages_md_show_preview' => 'Show preview',
|
||||
'pages_md_sync_scroll' => 'Sync preview scroll',
|
||||
'pages_not_in_chapter' => '本页面不在某章节中',
|
||||
'pages_move' => '移动页面',
|
||||
'pages_move_success' => '页面已移动到「:parentName」',
|
||||
@@ -236,14 +236,14 @@ return [
|
||||
'pages_permissions_success' => '页面权限已更新',
|
||||
'pages_revision' => '修订',
|
||||
'pages_revisions' => '页面修订',
|
||||
'pages_revisions_desc' => '下面列出的是该页面的所有过去修订。如果权限允许,您可以回顾、比较和恢复旧的页面版本。页面的完整历史可能不会在这里完全反映出来,因为根据系统配置,旧的修订可能会被自动删除。',
|
||||
'pages_revisions_desc' => 'Listed below are all the past revisions of this page. You can look back upon, compare, and restore old page versions if permissions allow. The full history of the page may not be fully reflected here since, depending on system configuration, old revisions could be auto-deleted.',
|
||||
'pages_revisions_named' => '“:pageName”页面修订',
|
||||
'pages_revision_named' => '“:pageName”页面修订',
|
||||
'pages_revision_restored_from' => '恢复到 #:id :summary',
|
||||
'pages_revisions_created_by' => '创建者',
|
||||
'pages_revisions_date' => '修订日期',
|
||||
'pages_revisions_number' => '#',
|
||||
'pages_revisions_sort_number' => '修订号',
|
||||
'pages_revisions_sort_number' => 'Revision Number',
|
||||
'pages_revisions_numbered' => '修订 #:id',
|
||||
'pages_revisions_numbered_changes' => '修改 #:id ',
|
||||
'pages_revisions_editor' => '编辑器类型',
|
||||
@@ -280,7 +280,7 @@ return [
|
||||
'shelf_tags' => '书架标签',
|
||||
'tag' => '标签',
|
||||
'tags' => '标签',
|
||||
'tags_index_desc' => '标签是一种灵活的分类形式,可以应用于系统内的内容。标签可以有一个键和值,值是可选的。应用后就可以使用标签的名称和值来搜索内容。',
|
||||
'tags_index_desc' => 'Tags can be applied to content within the system to apply a flexible form of categorization. Tags can have both a key and value, with the value being optional. Once applied, content can then be queried using the tag name and value.',
|
||||
'tag_name' => '标签名称',
|
||||
'tag_value' => '标签值 (可选)',
|
||||
'tags_explain' => "添加一些标签以更好地对您的内容进行分类。\n您可以为标签分配一个值,以进行更好的进行管理。",
|
||||
|
||||
@@ -30,14 +30,14 @@ return [
|
||||
'social_no_action_defined' => '没有定义行为',
|
||||
'social_login_bad_response' => "在 :socialAccount 登录时遇到错误:\n:error",
|
||||
'social_account_in_use' => ':socialAccount 账户已被使用,请尝试通过 :socialAccount 选项登录。',
|
||||
'social_account_email_in_use' => 'Email :email 已经被使用。如果您已有账户,则可以在个人资料设置中绑定您的 :socialAccount。',
|
||||
'social_account_email_in_use' => 'Email :email 已经被使用。如果您已有帐户,则可以在个人资料设置中绑定您的 :socialAccount。',
|
||||
'social_account_existing' => ':socialAccount已经被绑定到您的账户。',
|
||||
'social_account_already_used_existing' => ':socialAccount账户已经被其他用户使用。',
|
||||
'social_account_not_used' => ':socialAccount账户没有绑定到任何用户,请在您的个人资料设置中绑定。',
|
||||
'social_account_register_instructions' => '如果您还没有账户,您可以使用 :socialAccount 选项注册账户。',
|
||||
'social_account_register_instructions' => '如果您还没有帐户,您可以使用 :socialAccount 选项注册账户。',
|
||||
'social_driver_not_found' => '未找到社交驱动程序',
|
||||
'social_driver_not_configured' => '您的:socialAccount社交设置不正确。',
|
||||
'invite_token_expired' => '此邀请链接已过期。 您可以尝试重置您的账户密码。',
|
||||
'invite_token_expired' => '此邀请链接已过期。 您可以尝试重置您的帐户密码。',
|
||||
|
||||
// System
|
||||
'path_not_writable' => '无法上传到文件路径“:filePath”,请确保它可写入服务器。',
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
*/
|
||||
|
||||
return [
|
||||
'shortcuts' => '快捷键',
|
||||
'shortcuts_interface' => '界面键盘快捷键',
|
||||
'shortcuts_toggle_desc' => '你可以启用或禁用键盘系统界面快捷键,这些快捷键用于导航和操作。',
|
||||
'shortcuts_customize_desc' => '您可以自定义下面的每个快捷键。选择快捷方式输入后按下您想使用的按键组合即可。',
|
||||
'shortcuts_toggle_label' => '启用键盘快捷键',
|
||||
'shortcuts_section_navigation' => '导航',
|
||||
'shortcuts_section_actions' => '通用操作',
|
||||
'shortcuts_save' => '保存快捷键',
|
||||
'shortcuts_overlay_desc' => '注意:当快捷键启用时,可以按"?"键来打开帮助,它将突出显示当前屏幕上可见操作的快捷键。',
|
||||
'shortcuts_update_success' => '快捷键设置已更新!',
|
||||
'shortcuts' => 'Shortcuts',
|
||||
'shortcuts_interface' => 'Interface Keyboard Shortcuts',
|
||||
'shortcuts_toggle_desc' => 'Here you can enable or disable keyboard system interface shortcuts, used for navigation and actions.',
|
||||
'shortcuts_customize_desc' => 'You can customize each of the shortcuts below. Just press your desired key combination after selecting the input for a shortcut.',
|
||||
'shortcuts_toggle_label' => 'Keyboard shortcuts enabled',
|
||||
'shortcuts_section_navigation' => 'Navigation',
|
||||
'shortcuts_section_actions' => 'Common Actions',
|
||||
'shortcuts_save' => 'Save Shortcuts',
|
||||
'shortcuts_overlay_desc' => 'Note: When shortcuts are enabled a helper overlay is available via pressing "?" which will highlight the available shortcuts for actions currently visible on the screen.',
|
||||
'shortcuts_update_success' => 'Shortcut preferences have been updated!',
|
||||
];
|
||||
@@ -133,11 +133,11 @@ return [
|
||||
// Role Settings
|
||||
'roles' => '角色',
|
||||
'role_user_roles' => '用户角色',
|
||||
'roles_index_desc' => '角色用于对用户进行分组并为其成员提供系统权限。当一个用户是多个角色的成员时,授予的权限将叠加,用户将继承所有角色的能力。',
|
||||
'roles_x_users_assigned' => '1 位用户已分配|:count 位用户已分配',
|
||||
'roles_x_permissions_provided' => '1 个权限|:count 个权限',
|
||||
'roles_assigned_users' => '已分配用户',
|
||||
'roles_permissions_provided' => '已提供权限',
|
||||
'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.',
|
||||
'roles_x_users_assigned' => '1 user assigned|:count users assigned',
|
||||
'roles_x_permissions_provided' => '1 permission|:count permissions',
|
||||
'roles_assigned_users' => 'Assigned Users',
|
||||
'roles_permissions_provided' => 'Provided Permissions',
|
||||
'role_create' => '创建角色',
|
||||
'role_create_success' => '角色创建成功',
|
||||
'role_delete' => '删除角色',
|
||||
@@ -177,7 +177,7 @@ return [
|
||||
|
||||
// Users
|
||||
'users' => '用户',
|
||||
'users_index_desc' => '在系统内创建和管理个人用户账户。用户账户用于登录和内容及活动的归属。访问权限主要是基于角色的,但用户的内容所有权以及其他因素,也可能影响到权限和访问。',
|
||||
'users_index_desc' => 'Create & manage individual user accounts within the system. User accounts are used for login and attribution of content & activity. Access permissions are primarily role-based but user content ownership, among other factors, may also affect permissions & access.',
|
||||
'user_profile' => '用户资料',
|
||||
'users_add_new' => '添加用户',
|
||||
'users_search' => '搜索用户',
|
||||
@@ -209,7 +209,7 @@ return [
|
||||
'users_preferred_language' => '语言',
|
||||
'users_preferred_language_desc' => '此选项将更改用于应用程序用户界面的语言。 这不会影响任何用户创建的内容。',
|
||||
'users_social_accounts' => '社交账户',
|
||||
'users_social_accounts_info' => '在这里,您可以绑定您的其他账户,以便更快更轻松地登录。如果您选择解除绑定,之后将不能通过此社交账户登录,请设置社交账户来取消本App的访问权限。',
|
||||
'users_social_accounts_info' => '在这里,您可以绑定您的其他帐户,以便更快更轻松地登录。如果您选择解除绑定,之后将不能通过此社交账户登录,请设置社交账户来取消本App的访问权限。',
|
||||
'users_social_connect' => '绑定账户',
|
||||
'users_social_disconnect' => '解除绑定账户',
|
||||
'users_social_connected' => ':socialAccount 账户已经成功绑定到您的资料。',
|
||||
@@ -247,8 +247,8 @@ return [
|
||||
|
||||
// Webhooks
|
||||
'webhooks' => 'Webhooks',
|
||||
'webhooks_index_desc' => 'Webhook 是一种在系统内发生某些操作和事件时将数据发送到外部 URL 的方法,它允许与外部平台(例如消息传递或通知系统)进行基于事件的集成。',
|
||||
'webhooks_x_trigger_events' => '1 个触发事件 |:count 个触发事件',
|
||||
'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.',
|
||||
'webhooks_x_trigger_events' => '1 trigger event|:count trigger events',
|
||||
'webhooks_create' => '新建 Webhook',
|
||||
'webhooks_none_created' => '尚未创建任何 Webhook。',
|
||||
'webhooks_edit' => '编辑 Webhook',
|
||||
|
||||
@@ -318,6 +318,14 @@ body.tox-fullscreen, body.markdown-fullscreen {
|
||||
@include lightDark(color, #444, #EEE);
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
div[toolbox-tab-content] {
|
||||
padding-bottom: 45px;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
h4 {
|
||||
font-size: 24px;
|
||||
margin: $-m 0 0 0;
|
||||
@@ -351,8 +359,6 @@ body.tox-fullscreen, body.markdown-fullscreen {
|
||||
|
||||
.toolbox-tab-content {
|
||||
display: none;
|
||||
overflow-y: auto;
|
||||
padding-bottom: 45px;
|
||||
}
|
||||
|
||||
.suggestion-box {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
{{--
|
||||
$role - The Role to display this row for.
|
||||
$modelType - The type of permission model; String matching one of: user, role, fallback
|
||||
$modelId - The ID of the permission model.
|
||||
$modelName - The name of the permission model.
|
||||
$modelDescription - The description of the permission model.
|
||||
$entityType - String identifier for type of entity having permissions applied.
|
||||
$permission - The entity permission containing the permissions.
|
||||
$inheriting - Boolean if the current row should be marked as inheriting default permissions. Used for "Everyone Else" role.
|
||||
@@ -7,21 +10,21 @@ $inheriting - Boolean if the current row should be marked as inheriting default
|
||||
|
||||
<div component="permissions-table" class="item-list-row flex-container-row justify-space-between wrap">
|
||||
<div class="gap-x-m flex-container-row items-center px-l py-m flex">
|
||||
<div class="text-large" title="{{ $role->id === 0 ? trans('entities.permissions_role_everyone_else') : trans('common.role') }}">
|
||||
@icon($role->id === 0 ? 'groups' : 'role')
|
||||
<div class="text-large" title="{{ $modelType === 'fallback' ? trans('entities.permissions_role_everyone_else') : ($modelType === 'role' ? trans('common.role') : trans('common.user')) }}">
|
||||
@icon($modelType === 'fallback' ? 'groups' : ($modelType === 'role' ? 'role' : 'user'))
|
||||
</div>
|
||||
<span>
|
||||
<strong>{{ $role->display_name }}</strong> <br>
|
||||
<small class="text-muted">{{ $role->description }}</small>
|
||||
<strong>{{ $modelName }}</strong> <br>
|
||||
<small class="text-muted">{{ $modelDescription }}</small>
|
||||
</span>
|
||||
@if($role->id !== 0)
|
||||
@if($modelType !== 'fallback')
|
||||
<button type="button"
|
||||
class="ml-auto flex-none text-small text-primary text-button hover-underline item-list-row-toggle-all hide-under-s"
|
||||
refs="permissions-table@toggle-all"
|
||||
><strong>{{ trans('common.toggle_all') }}</strong></button>
|
||||
@endif
|
||||
</div>
|
||||
@if($role->id === 0)
|
||||
@if($modelType === 'fallback')
|
||||
<div class="px-l flex-container-row items-center" refs="entity-permissions@everyone-inherit">
|
||||
@include('form.custom-checkbox', [
|
||||
'name' => 'entity-permissions-inherit',
|
||||
@@ -32,12 +35,12 @@ $inheriting - Boolean if the current row should be marked as inheriting default
|
||||
</div>
|
||||
@endif
|
||||
<div class="flex-container-row justify-space-between gap-x-xl wrap items-center">
|
||||
<input type="hidden" name="permissions[{{ $role->id }}][active]"
|
||||
<input type="hidden" name="permissions[{{ $modelType }}][{{ $modelId }}][active]"
|
||||
@if($inheriting) disabled="disabled" @endif
|
||||
value="true">
|
||||
<div class="px-l">
|
||||
@include('form.custom-checkbox', [
|
||||
'name' => 'permissions[' . $role->id . '][view]',
|
||||
'name' => 'permissions[' . $modelType . '][' . $modelId . '][view]',
|
||||
'label' => trans('common.view'),
|
||||
'value' => 'true',
|
||||
'checked' => $permission->view,
|
||||
@@ -47,7 +50,7 @@ $inheriting - Boolean if the current row should be marked as inheriting default
|
||||
@if($entityType !== 'page')
|
||||
<div class="px-l">
|
||||
@include('form.custom-checkbox', [
|
||||
'name' => 'permissions[' . $role->id . '][create]',
|
||||
'name' => 'permissions[' . $modelType . '][' . $modelId . '][create]',
|
||||
'label' => trans('common.create'),
|
||||
'value' => 'true',
|
||||
'checked' => $permission->create,
|
||||
@@ -57,7 +60,7 @@ $inheriting - Boolean if the current row should be marked as inheriting default
|
||||
@endif
|
||||
<div class="px-l">
|
||||
@include('form.custom-checkbox', [
|
||||
'name' => 'permissions[' . $role->id . '][update]',
|
||||
'name' => 'permissions[' . $modelType . '][' . $modelId . '][update]',
|
||||
'label' => trans('common.update'),
|
||||
'value' => 'true',
|
||||
'checked' => $permission->update,
|
||||
@@ -66,7 +69,7 @@ $inheriting - Boolean if the current row should be marked as inheriting default
|
||||
</div>
|
||||
<div class="px-l">
|
||||
@include('form.custom-checkbox', [
|
||||
'name' => 'permissions[' . $role->id . '][delete]',
|
||||
'name' => 'permissions[' . $modelType . '][' . $modelId . '][delete]',
|
||||
'label' => trans('common.delete'),
|
||||
'value' => 'true',
|
||||
'checked' => $permission->delete,
|
||||
@@ -74,12 +77,13 @@ $inheriting - Boolean if the current row should be marked as inheriting default
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
@if($role->id !== 0)
|
||||
@if($modelType !== 'fallback')
|
||||
<div class="flex-container-row items-center px-m py-s">
|
||||
<button type="button"
|
||||
class="text-neg p-m icon-button"
|
||||
data-role-id="{{ $role->id }}"
|
||||
data-role-name="{{ $role->display_name }}"
|
||||
data-model-type="{{ $modelType }}"
|
||||
data-model-id="{{ $modelId }}"
|
||||
data-model-name="{{ $modelName }}"
|
||||
title="{{ trans('common.remove') }}">
|
||||
@icon('close') <span class="hide-over-m ml-xs">{{ trans('common.remove') }}</span>
|
||||
</button>
|
||||
|
||||
@@ -35,11 +35,35 @@
|
||||
|
||||
<hr>
|
||||
|
||||
<div refs="entity-permissions@user-container" class="item-list mt-m mb-m">
|
||||
@foreach($data->permissionsWithUsers() as $permission)
|
||||
@include('form.entity-permissions-row', [
|
||||
'permission' => $permission,
|
||||
'modelType' => 'user',
|
||||
'modelId' => $permission->user->id,
|
||||
'modelName' => $permission->user->name,
|
||||
'modelDescription' => '',
|
||||
'entityType' => $model->getType(),
|
||||
'inheriting' => false,
|
||||
])
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<div class="flex-container-row justify-flex-end mb-xl">
|
||||
<div refs="entity-permissions@user-select-container" class="flex-container-row items-center gap-m">
|
||||
<label for="user_select" class="m-none p-none"><span class="bold">{{ trans('entities.permissions_user_override') }}</span></label>
|
||||
@include('form.user-select', ['name' => 'user_select', 'user' => null])
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div refs="entity-permissions@role-container" class="item-list mt-m mb-m">
|
||||
@foreach($data->permissionsWithRoles() as $permission)
|
||||
@include('form.entity-permissions-row', [
|
||||
'permission' => $permission,
|
||||
'role' => $permission->role,
|
||||
'modelType' => 'role',
|
||||
'modelId' => $permission->role->id,
|
||||
'modelName' => $permission->role->display_name,
|
||||
'modelDescription' => $permission->role->description,
|
||||
'entityType' => $model->getType(),
|
||||
'inheriting' => false,
|
||||
])
|
||||
@@ -60,10 +84,13 @@
|
||||
|
||||
<div class="item-list mt-m mb-xl">
|
||||
@include('form.entity-permissions-row', [
|
||||
'role' => $data->everyoneElseRole(),
|
||||
'modelType' => 'fallback',
|
||||
'modelId' => 0,
|
||||
'modelName' => trans('entities.permissions_role_everyone_else'),
|
||||
'modelDescription' => trans('entities.permissions_role_everyone_else_desc'),
|
||||
'permission' => $data->everyoneElseEntityPermission(),
|
||||
'entityType' => $model->getType(),
|
||||
'inheriting' => !$model->permissions()->where('role_id', '=', 0)->exists(),
|
||||
'inheriting' => $data->everyoneElseInheriting(),
|
||||
])
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
|
||||
<div class="toggle-switch-list dual-column-content">
|
||||
<input type="hidden" name="{{ $name }}[0]" value="0">
|
||||
@foreach($roles as $role)
|
||||
<div>
|
||||
@include('form.custom-checkbox', [
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
$languages = [
|
||||
'Bash', 'CSS', 'C', 'C++', 'C#', 'Dart', 'Diff', 'Fortran', 'F#', 'Go', 'Haskell', 'HTML', 'INI',
|
||||
'Java', 'JavaScript', 'JSON', 'Julia', 'Kotlin', 'LaTeX', 'Lua', 'MarkDown', 'MATLAB', 'Nginx', 'OCaml',
|
||||
'Octave', 'Pascal', 'Perl', 'PHP', 'Powershell', 'Python', 'Ruby', 'Rust', 'Shell', 'Smarty', 'SQL', 'Swift',
|
||||
'Twig', 'TypeScript', 'VBScript', 'VB.NET', 'XML', 'YAML',
|
||||
'Octave', 'Pascal', 'Perl', 'PHP', 'Powershell', 'Python', 'Ruby', 'Rust', 'Shell', 'SQL', 'Swift',
|
||||
'TypeScript', 'VBScript', 'VB.NET', 'XML', 'YAML',
|
||||
];
|
||||
@endphp
|
||||
|
||||
|
||||
@@ -217,7 +217,8 @@ Route::middleware('auth')->group(function () {
|
||||
Route::get('/home', [HomeController::class, 'index']);
|
||||
|
||||
// Permissions
|
||||
Route::get('/permissions/form-row/{entityType}/{roleId}', [PermissionsController::class, 'formRowForRole']);
|
||||
Route::get('/permissions/role-form-row/{entityType}/{roleId}', [PermissionsController::class, 'formRowForRole']);
|
||||
Route::get('/permissions/user-form-row/{entityType}/{userId}', [PermissionsController::class, 'formRowForUser']);
|
||||
|
||||
// Maintenance
|
||||
Route::get('/settings/maintenance', [MaintenanceController::class, 'index']);
|
||||
|
||||
@@ -23,17 +23,17 @@ class AuditLogTest extends TestCase
|
||||
|
||||
public function test_only_accessible_with_right_permissions()
|
||||
{
|
||||
$viewer = $this->getViewer();
|
||||
$viewer = $this->users->viewer();
|
||||
$this->actingAs($viewer);
|
||||
|
||||
$resp = $this->get('/settings/audit');
|
||||
$this->assertPermissionError($resp);
|
||||
|
||||
$this->giveUserPermissions($viewer, ['settings-manage']);
|
||||
$this->permissions->grantUserRolePermissions($viewer, ['settings-manage']);
|
||||
$resp = $this->get('/settings/audit');
|
||||
$this->assertPermissionError($resp);
|
||||
|
||||
$this->giveUserPermissions($viewer, ['users-manage']);
|
||||
$this->permissions->grantUserRolePermissions($viewer, ['users-manage']);
|
||||
$resp = $this->get('/settings/audit');
|
||||
$resp->assertStatus(200);
|
||||
$resp->assertSeeText('Audit Log');
|
||||
@@ -41,7 +41,7 @@ class AuditLogTest extends TestCase
|
||||
|
||||
public function test_shows_activity()
|
||||
{
|
||||
$admin = $this->getAdmin();
|
||||
$admin = $this->users->admin();
|
||||
$this->actingAs($admin);
|
||||
$page = $this->entities->page();
|
||||
$this->activityService->add(ActivityType::PAGE_CREATE, $page);
|
||||
@@ -56,7 +56,7 @@ class AuditLogTest extends TestCase
|
||||
|
||||
public function test_shows_name_for_deleted_items()
|
||||
{
|
||||
$this->actingAs($this->getAdmin());
|
||||
$this->actingAs($this->users->admin());
|
||||
$page = $this->entities->page();
|
||||
$pageName = $page->name;
|
||||
$this->activityService->add(ActivityType::PAGE_CREATE, $page);
|
||||
@@ -71,12 +71,12 @@ class AuditLogTest extends TestCase
|
||||
|
||||
public function test_shows_activity_for_deleted_users()
|
||||
{
|
||||
$viewer = $this->getViewer();
|
||||
$viewer = $this->users->viewer();
|
||||
$this->actingAs($viewer);
|
||||
$page = $this->entities->page();
|
||||
$this->activityService->add(ActivityType::PAGE_CREATE, $page);
|
||||
|
||||
$this->actingAs($this->getAdmin());
|
||||
$this->actingAs($this->users->admin());
|
||||
app(UserRepo::class)->destroy($viewer);
|
||||
|
||||
$resp = $this->get('settings/audit');
|
||||
@@ -85,7 +85,7 @@ class AuditLogTest extends TestCase
|
||||
|
||||
public function test_filters_by_key()
|
||||
{
|
||||
$this->actingAs($this->getAdmin());
|
||||
$this->actingAs($this->users->admin());
|
||||
$page = $this->entities->page();
|
||||
$this->activityService->add(ActivityType::PAGE_CREATE, $page);
|
||||
|
||||
@@ -98,7 +98,7 @@ class AuditLogTest extends TestCase
|
||||
|
||||
public function test_date_filters()
|
||||
{
|
||||
$this->actingAs($this->getAdmin());
|
||||
$this->actingAs($this->users->admin());
|
||||
$page = $this->entities->page();
|
||||
$this->activityService->add(ActivityType::PAGE_CREATE, $page);
|
||||
|
||||
@@ -120,8 +120,8 @@ class AuditLogTest extends TestCase
|
||||
|
||||
public function test_user_filter()
|
||||
{
|
||||
$admin = $this->getAdmin();
|
||||
$editor = $this->getEditor();
|
||||
$admin = $this->users->admin();
|
||||
$editor = $this->users->editor();
|
||||
$this->actingAs($admin);
|
||||
$page = $this->entities->page();
|
||||
$this->activityService->add(ActivityType::PAGE_CREATE, $page);
|
||||
@@ -142,7 +142,7 @@ class AuditLogTest extends TestCase
|
||||
public function test_ip_address_logged_and_visible()
|
||||
{
|
||||
config()->set('app.proxies', '*');
|
||||
$editor = $this->getEditor();
|
||||
$editor = $this->users->editor();
|
||||
$page = $this->entities->page();
|
||||
|
||||
$this->actingAs($editor)->put($page->getUrl(), [
|
||||
@@ -166,7 +166,7 @@ class AuditLogTest extends TestCase
|
||||
public function test_ip_address_is_searchable()
|
||||
{
|
||||
config()->set('app.proxies', '*');
|
||||
$editor = $this->getEditor();
|
||||
$editor = $this->users->editor();
|
||||
$page = $this->entities->page();
|
||||
|
||||
$this->actingAs($editor)->put($page->getUrl(), [
|
||||
@@ -192,7 +192,7 @@ class AuditLogTest extends TestCase
|
||||
{
|
||||
config()->set('app.proxies', '*');
|
||||
config()->set('app.env', 'demo');
|
||||
$editor = $this->getEditor();
|
||||
$editor = $this->users->editor();
|
||||
$page = $this->entities->page();
|
||||
|
||||
$this->actingAs($editor)->put($page->getUrl(), [
|
||||
@@ -215,7 +215,7 @@ class AuditLogTest extends TestCase
|
||||
{
|
||||
config()->set('app.proxies', '*');
|
||||
config()->set('app.ip_address_precision', 2);
|
||||
$editor = $this->getEditor();
|
||||
$editor = $this->users->editor();
|
||||
$page = $this->entities->page();
|
||||
|
||||
$this->actingAs($editor)->put($page->getUrl(), [
|
||||
|
||||
@@ -88,7 +88,7 @@ class WebhookCallTest extends TestCase
|
||||
]);
|
||||
$webhook = $this->newWebhook(['active' => true, 'endpoint' => 'https://wh.example.com'], ['all']);
|
||||
$page = $this->entities->page();
|
||||
$editor = $this->getEditor();
|
||||
$editor = $this->users->editor();
|
||||
|
||||
$this->runEvent(ActivityType::PAGE_UPDATE, $page, $editor);
|
||||
|
||||
@@ -111,7 +111,7 @@ class WebhookCallTest extends TestCase
|
||||
protected function runEvent(string $event, $detail = '', ?User $user = null)
|
||||
{
|
||||
if (is_null($user)) {
|
||||
$user = $this->getEditor();
|
||||
$user = $this->users->editor();
|
||||
}
|
||||
|
||||
$this->actingAs($user);
|
||||
|
||||
@@ -41,7 +41,7 @@ class WebhookFormatTesting extends TestCase
|
||||
protected function getWebhookData(string $event, $detail): array
|
||||
{
|
||||
$webhook = Webhook::factory()->make();
|
||||
$user = $this->getEditor();
|
||||
$user = $this->users->editor();
|
||||
$formatter = WebhookFormatter::getDefault($event, $webhook, $detail, $user, time());
|
||||
|
||||
return $formatter->format();
|
||||
|
||||
@@ -135,7 +135,7 @@ class WebhookManagementTest extends TestCase
|
||||
|
||||
public function test_settings_manage_permission_required_for_webhook_routes()
|
||||
{
|
||||
$editor = $this->getEditor();
|
||||
$editor = $this->users->editor();
|
||||
$this->actingAs($editor);
|
||||
|
||||
$routes = [
|
||||
@@ -153,7 +153,7 @@ class WebhookManagementTest extends TestCase
|
||||
$this->assertPermissionError($resp);
|
||||
}
|
||||
|
||||
$this->giveUserPermissions($editor, ['settings-manage']);
|
||||
$this->permissions->grantUserRolePermissions($editor, ['settings-manage']);
|
||||
|
||||
foreach ($routes as [$method, $endpoint]) {
|
||||
$resp = $this->call($method, $endpoint);
|
||||
|
||||
@@ -16,8 +16,8 @@ class ApiAuthTest extends TestCase
|
||||
|
||||
public function test_requests_succeed_with_default_auth()
|
||||
{
|
||||
$viewer = $this->getViewer();
|
||||
$this->giveUserPermissions($viewer, ['access-api']);
|
||||
$viewer = $this->users->viewer();
|
||||
$this->permissions->grantUserRolePermissions($viewer, ['access-api']);
|
||||
|
||||
$resp = $this->get($this->endpoint);
|
||||
$resp->assertStatus(401);
|
||||
@@ -63,7 +63,7 @@ class ApiAuthTest extends TestCase
|
||||
auth()->logout();
|
||||
|
||||
$accessApiPermission = RolePermission::getByName('access-api');
|
||||
$editorRole = $this->getEditor()->roles()->first();
|
||||
$editorRole = $this->users->editor()->roles()->first();
|
||||
$editorRole->detachPermission($accessApiPermission);
|
||||
|
||||
$resp = $this->get($this->endpoint, $this->apiAuthHeader());
|
||||
@@ -73,7 +73,7 @@ class ApiAuthTest extends TestCase
|
||||
|
||||
public function test_api_access_permission_required_to_access_api_with_session_auth()
|
||||
{
|
||||
$editor = $this->getEditor();
|
||||
$editor = $this->users->editor();
|
||||
$this->actingAs($editor, 'standard');
|
||||
|
||||
$resp = $this->get($this->endpoint);
|
||||
@@ -81,7 +81,7 @@ class ApiAuthTest extends TestCase
|
||||
auth('standard')->logout();
|
||||
|
||||
$accessApiPermission = RolePermission::getByName('access-api');
|
||||
$editorRole = $this->getEditor()->roles()->first();
|
||||
$editorRole = $this->users->editor()->roles()->first();
|
||||
$editorRole->detachPermission($accessApiPermission);
|
||||
|
||||
$editor = User::query()->where('id', '=', $editor->id)->first();
|
||||
@@ -114,7 +114,7 @@ class ApiAuthTest extends TestCase
|
||||
|
||||
public function test_token_expiry_checked()
|
||||
{
|
||||
$editor = $this->getEditor();
|
||||
$editor = $this->users->editor();
|
||||
$token = $editor->apiTokens()->first();
|
||||
|
||||
$resp = $this->get($this->endpoint, $this->apiAuthHeader());
|
||||
@@ -130,7 +130,7 @@ class ApiAuthTest extends TestCase
|
||||
|
||||
public function test_email_confirmation_checked_using_api_auth()
|
||||
{
|
||||
$editor = $this->getEditor();
|
||||
$editor = $this->users->editor();
|
||||
$editor->email_confirmed = false;
|
||||
$editor->save();
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ class AttachmentsApiTest extends TestCase
|
||||
],
|
||||
]]);
|
||||
|
||||
$this->entities->setPermissions($page, [], []);
|
||||
$this->permissions->setEntityPermissions($page, [], []);
|
||||
|
||||
$resp = $this->getJson($this->baseEndpoint . '?count=1&sort=+id');
|
||||
$resp->assertJsonMissing(['data' => [
|
||||
@@ -246,13 +246,13 @@ class AttachmentsApiTest extends TestCase
|
||||
public function test_attachment_not_visible_on_other_users_draft()
|
||||
{
|
||||
$this->actingAsApiAdmin();
|
||||
$editor = $this->getEditor();
|
||||
$editor = $this->users->editor();
|
||||
|
||||
$page = $this->entities->page();
|
||||
$page->draft = true;
|
||||
$page->owned_by = $editor->id;
|
||||
$page->save();
|
||||
$this->entities->regenPermissions($page);
|
||||
$this->permissions->regenerateForEntity($page);
|
||||
|
||||
$attachment = $this->createAttachmentForPage($page, [
|
||||
'name' => 'my attachment',
|
||||
@@ -342,7 +342,7 @@ class AttachmentsApiTest extends TestCase
|
||||
|
||||
protected function createAttachmentForPage(Page $page, $attributes = []): Attachment
|
||||
{
|
||||
$admin = $this->getAdmin();
|
||||
$admin = $this->users->admin();
|
||||
/** @var Attachment $attachment */
|
||||
$attachment = $page->attachments()->forceCreate(array_merge([
|
||||
'uploaded_to' => $page->id,
|
||||
|
||||
@@ -246,7 +246,7 @@ class BooksApiTest extends TestCase
|
||||
{
|
||||
$types = ['html', 'plaintext', 'pdf', 'markdown'];
|
||||
$this->actingAsApiEditor();
|
||||
$this->removePermissionFromUser($this->getEditor(), 'content-export');
|
||||
$this->permissions->removeUserRolePermissions($this->users->editor(), ['content-export']);
|
||||
|
||||
$book = $this->entities->book();
|
||||
foreach ($types as $type) {
|
||||
|
||||
@@ -221,7 +221,7 @@ class ChaptersApiTest extends TestCase
|
||||
{
|
||||
$types = ['html', 'plaintext', 'pdf', 'markdown'];
|
||||
$this->actingAsApiEditor();
|
||||
$this->removePermissionFromUser($this->getEditor(), 'content-export');
|
||||
$this->permissions->removeUserRolePermissions($this->users->editor(), ['content-export']);
|
||||
|
||||
$chapter = Chapter::visible()->has('pages')->first();
|
||||
foreach ($types as $type) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user