mirror of
https://github.com/BookStackApp/BookStack.git
synced 2026-02-09 03:09:38 +03:00
Compare commits
177 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92d15d9cf2 | ||
|
|
b06147fef7 | ||
|
|
776ec7b9e7 | ||
|
|
8aa6bdc8ab | ||
|
|
4ab17157b1 | ||
|
|
6d7ffab115 | ||
|
|
c8cfec96dc | ||
|
|
d145efb6f6 | ||
|
|
c54101c603 | ||
|
|
865e5aecc9 | ||
|
|
ae4d1d804a | ||
|
|
5fc19b0edf | ||
|
|
0a73b70b64 | ||
|
|
2668aae09b | ||
|
|
3b9c0b34ae | ||
|
|
53f32849a9 | ||
|
|
7ca8bdc231 | ||
|
|
6621d55f3d | ||
|
|
d55db06c01 | ||
|
|
6b4b500a33 | ||
|
|
5ffec2c52d | ||
|
|
ec07793cda | ||
|
|
61adc735c8 | ||
|
|
7bbf591a7f | ||
|
|
61f8d18af5 | ||
|
|
f786d25f2e | ||
|
|
e62f4426ea | ||
|
|
32ba3a591f | ||
|
|
73025719a4 | ||
|
|
d55684531f | ||
|
|
d15eb129b0 | ||
|
|
3626a2265b | ||
|
|
d13abc7e1d | ||
|
|
2442829ef2 | ||
|
|
795b28162a | ||
|
|
31706ea06b | ||
|
|
4b9e6042d5 | ||
|
|
d279b0830b | ||
|
|
181ab91b1d | ||
|
|
841350a937 | ||
|
|
12183bac07 | ||
|
|
306f41b6f0 | ||
|
|
c1d76d2571 | ||
|
|
f83074d50e | ||
|
|
2be892be70 | ||
|
|
c934b9319f | ||
|
|
35a51197ce | ||
|
|
47fd578edb | ||
|
|
add091305c | ||
|
|
3d017594a8 | ||
|
|
0dcb2ec78c | ||
|
|
9186e77d27 | ||
|
|
6045aff33a | ||
|
|
dca9765d5d | ||
|
|
a37d0c57dc | ||
|
|
054475135a | ||
|
|
02a35b6db4 | ||
|
|
b80992ca59 | ||
|
|
c606970e38 | ||
|
|
dfeca246a0 | ||
|
|
3476d83ecc | ||
|
|
3617ab1540 | ||
|
|
e65b4b63a2 | ||
|
|
7cac3f4780 | ||
|
|
c4839c783a | ||
|
|
a5751a584c | ||
|
|
f518a3be37 | ||
|
|
0208f066c5 | ||
|
|
2d0461b63a | ||
|
|
b913ae703d | ||
|
|
1611b0399f | ||
|
|
8d4b8ff4f3 | ||
|
|
77a88618c2 | ||
|
|
8b062d4795 | ||
|
|
717b516341 | ||
|
|
fda242d3da | ||
|
|
aac547934c | ||
|
|
92cd11d105 | ||
|
|
13115ace84 | ||
|
|
5c9b90ea0d | ||
|
|
074f193e2f | ||
|
|
7f2604c8e8 | ||
|
|
b71b2a4376 | ||
|
|
68df43e5a8 | ||
|
|
c5ca865723 | ||
|
|
b862f12a50 | ||
|
|
b0f8b11054 | ||
|
|
7650ebf2f9 | ||
|
|
d9ea52522e | ||
|
|
2e718c12e1 | ||
|
|
a43a1832f5 | ||
|
|
c4f7368c1c | ||
|
|
2a32475541 | ||
|
|
1243108e0f | ||
|
|
3280919370 | ||
|
|
d149b809b1 | ||
|
|
eb47e11916 | ||
|
|
9d6bc1ad4d | ||
|
|
30bf0ce632 | ||
|
|
b64c9b31d5 | ||
|
|
f9dbbe5d70 | ||
|
|
05f7f4cb17 | ||
|
|
454b152b95 | ||
|
|
b29fe5c46d | ||
|
|
131ac29df4 | ||
|
|
3a9d18a6cd | ||
|
|
59e2c5e52a | ||
|
|
d29b14ebfd | ||
|
|
73f9834e6f | ||
|
|
3afe855156 | ||
|
|
cdd446ac73 | ||
|
|
1dd1024eba | ||
|
|
752cfe2f67 | ||
|
|
25baaa8189 | ||
|
|
d2d0331782 | ||
|
|
8121418e18 | ||
|
|
5ab31a8191 | ||
|
|
0e69ab1938 | ||
|
|
058007109e | ||
|
|
32b29fcdfc | ||
|
|
8f92b6f21b | ||
|
|
62f78f1c6d | ||
|
|
f8c0aaff03 | ||
|
|
a27df485bb | ||
|
|
bfde896f0b | ||
|
|
1cdc0a7a3d | ||
|
|
3e99ce4098 | ||
|
|
d19b86640b | ||
|
|
2936ba609b | ||
|
|
ce1e20501c | ||
|
|
295532fa7a | ||
|
|
642ba668b1 | ||
|
|
4f36cdd757 | ||
|
|
8821844c4a | ||
|
|
1262083fcf | ||
|
|
c82fa33210 | ||
|
|
15c79c38db | ||
|
|
e7dcc2dcdf | ||
|
|
099f6104d0 | ||
|
|
8bdf948743 | ||
|
|
e8f44186a8 | ||
|
|
ecda4e1d6f | ||
|
|
64da80cbf4 | ||
|
|
5fa728f28a | ||
|
|
c61ce8dee4 | ||
|
|
f656a82fe7 | ||
|
|
5bfba281fc | ||
|
|
18ede9bbd3 | ||
|
|
2e7544a865 | ||
|
|
5e3c3ad634 | ||
|
|
add238fe9f | ||
|
|
8d159f77e4 | ||
|
|
573a2dd22a | ||
|
|
b55cc803d3 | ||
|
|
fa566f156a | ||
|
|
78a0a2f519 | ||
|
|
42cbd6adef | ||
|
|
6117349893 | ||
|
|
1256320c72 | ||
|
|
1ba0d26fdd | ||
|
|
802f69cf35 | ||
|
|
bb44334224 | ||
|
|
9bfcadd95f | ||
|
|
62c8eb3357 | ||
|
|
c03e44124a | ||
|
|
5c6671b3bf | ||
|
|
abe7467ae5 | ||
|
|
304ade418e | ||
|
|
997931c42f | ||
|
|
0ec0913846 | ||
|
|
e980564fd6 | ||
|
|
8a9215ecad | ||
|
|
304a1d8f91 | ||
|
|
dfbc78947f | ||
|
|
4f5ad171ac | ||
|
|
94b1cffa2d | ||
|
|
da82e70ca3 |
@@ -56,6 +56,7 @@ APP_PROXIES=null
|
||||
|
||||
# Database details
|
||||
# Host can contain a port (localhost:3306) or a separate DB_PORT option can be used.
|
||||
# An ipv6 address can be used via the square bracket format ([::1]).
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=database_database
|
||||
|
||||
1
.github/ISSUE_TEMPLATE/support_request.yml
vendored
1
.github/ISSUE_TEMPLATE/support_request.yml
vendored
@@ -42,6 +42,7 @@ body:
|
||||
label: Log Content
|
||||
description: If the issue has produced an error, provide any [BookStack or server log](https://www.bookstackapp.com/docs/admin/debugging/) content below.
|
||||
placeholder: Be sure to remove any confidential details in your logs
|
||||
render: text
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
|
||||
9
.github/ISSUE_TEMPLATE/z_blank_request.yml
vendored
Normal file
9
.github/ISSUE_TEMPLATE/z_blank_request.yml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
name: Blank Request (Maintainers Only)
|
||||
description: For maintainers only - Start a blank request
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "**This blank request option is only for existing official maintainers of the project!** Please instead use a different request option. If you use this your issue will be closed off."
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
31
.github/translators.txt
vendored
31
.github/translators.txt
vendored
@@ -469,3 +469,34 @@ Raphael Moreno (RaphaelMoreno) :: Portuguese, Brazilian
|
||||
yn (user99) :: Arabic
|
||||
Pavel Zlatarov (pzlatarov) :: Bulgarian
|
||||
ingelres :: French
|
||||
mabdullah :: Arabic
|
||||
Skrabák Csaba (kekcsi) :: Hungarian
|
||||
Evert Meulie (Evert) :: Norwegian Bokmal
|
||||
Jasper Backer (jasperb) :: Dutch
|
||||
Alexandar Cavdarovski (ace.200112) :: Swedish
|
||||
구닥다리TV (yjj8353) :: Korean
|
||||
Onur Oskay (o.oskay) :: Turkish
|
||||
Sébastien Merveille (SebastienMerv) :: French
|
||||
Maxim Kouznetsov (masya.work) :: Hebrew
|
||||
neodvisnost :: Slovenian
|
||||
Soubi Agatsuma (bisouya) :: Hebrew
|
||||
Ilya Shaulov (ishaulov) :: Russian
|
||||
Konstantin Bobkov (b.konstantv) :: Russian
|
||||
Ruben Sutter (rubensutter) :: German
|
||||
jellium :: French
|
||||
Qxlkdr :: Swedish
|
||||
Hari (muhhari) :: Indonesian
|
||||
仙君御 (xjy) :: Chinese Simplified
|
||||
TapioM :: Finnish
|
||||
lingb58 :: Chinese Traditional
|
||||
Angel Pandey (angel-pandey) :: Nepali
|
||||
Supriya Shrestha (supriyashrestha) :: Nepali
|
||||
gprabhat :: Nepali
|
||||
CellCat :: Chinese Simplified
|
||||
Al Desrahim (aldesrahim) :: Indonesian
|
||||
ahmad abbaspour (deshneh.dar.diss) :: Persian
|
||||
Erjon K. (ekr) :: Albanian
|
||||
LiZerui (iamzrli) :: Chinese Traditional
|
||||
Ticker (ticker.com) :: Hebrew
|
||||
CrazyComputer :: Chinese Simplified
|
||||
Firr (FirrV) :: Russian
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -32,3 +32,4 @@ webpack-stats.json
|
||||
phpstan.neon
|
||||
esbuild-meta.json
|
||||
.phpactor.json
|
||||
/*.zip
|
||||
|
||||
@@ -11,6 +11,7 @@ use BookStack\Exceptions\UserRegistrationException;
|
||||
use BookStack\Facades\Theme;
|
||||
use BookStack\Http\HttpRequestService;
|
||||
use BookStack\Theming\ThemeEvents;
|
||||
use BookStack\Uploads\UserAvatars;
|
||||
use BookStack\Users\Models\User;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use League\OAuth2\Client\OptionProvider\HttpBasicAuthOptionProvider;
|
||||
@@ -26,7 +27,8 @@ class OidcService
|
||||
protected RegistrationService $registrationService,
|
||||
protected LoginService $loginService,
|
||||
protected HttpRequestService $http,
|
||||
protected GroupSyncService $groupService
|
||||
protected GroupSyncService $groupService,
|
||||
protected UserAvatars $userAvatars
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -220,6 +222,10 @@ class OidcService
|
||||
throw new OidcException($exception->getMessage());
|
||||
}
|
||||
|
||||
if ($this->config()['fetch_avatar'] && !$user->avatar()->exists() && $userDetails->picture) {
|
||||
$this->userAvatars->assignToUserFromUrl($user, $userDetails->picture);
|
||||
}
|
||||
|
||||
if ($this->shouldSyncGroups()) {
|
||||
$detachExisting = $this->config()['remove_from_groups'];
|
||||
$this->groupService->syncUserWithFoundGroups($user, $userDetails->groups ?? [], $detachExisting);
|
||||
|
||||
@@ -11,6 +11,7 @@ class OidcUserDetails
|
||||
public ?string $email = null,
|
||||
public ?string $name = null,
|
||||
public ?array $groups = null,
|
||||
public ?string $picture = null,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -40,15 +41,16 @@ class OidcUserDetails
|
||||
$this->email = $claims->getClaim('email') ?? $this->email;
|
||||
$this->name = static::getUserDisplayName($displayNameClaims, $claims) ?? $this->name;
|
||||
$this->groups = static::getUserGroups($groupsClaim, $claims) ?? $this->groups;
|
||||
$this->picture = static::getPicture($claims) ?: $this->picture;
|
||||
}
|
||||
|
||||
protected static function getUserDisplayName(string $displayNameClaims, ProvidesClaims $token): string
|
||||
protected static function getUserDisplayName(string $displayNameClaims, ProvidesClaims $claims): string
|
||||
{
|
||||
$displayNameClaimParts = explode('|', $displayNameClaims);
|
||||
|
||||
$displayName = [];
|
||||
foreach ($displayNameClaimParts as $claim) {
|
||||
$component = $token->getClaim(trim($claim)) ?? '';
|
||||
$component = $claims->getClaim(trim($claim)) ?? '';
|
||||
if ($component !== '') {
|
||||
$displayName[] = $component;
|
||||
}
|
||||
@@ -57,13 +59,13 @@ class OidcUserDetails
|
||||
return implode(' ', $displayName);
|
||||
}
|
||||
|
||||
protected static function getUserGroups(string $groupsClaim, ProvidesClaims $token): ?array
|
||||
protected static function getUserGroups(string $groupsClaim, ProvidesClaims $claims): ?array
|
||||
{
|
||||
if (empty($groupsClaim)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$groupsList = Arr::get($token->getAllClaims(), $groupsClaim);
|
||||
$groupsList = Arr::get($claims->getAllClaims(), $groupsClaim);
|
||||
if (!is_array($groupsList)) {
|
||||
return null;
|
||||
}
|
||||
@@ -72,4 +74,14 @@ class OidcUserDetails
|
||||
return is_string($val);
|
||||
}));
|
||||
}
|
||||
|
||||
protected static function getPicture(ProvidesClaims $claims): ?string
|
||||
{
|
||||
$picture = $claims->getClaim('picture');
|
||||
if (is_string($picture) && str_starts_with($picture, 'http')) {
|
||||
return $picture;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ namespace BookStack\Activity;
|
||||
|
||||
use BookStack\Activity\Models\Comment;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Exceptions\NotifyException;
|
||||
use BookStack\Exceptions\PrettyException;
|
||||
use BookStack\Facades\Activity as ActivityService;
|
||||
use BookStack\Util\HtmlDescriptionFilter;
|
||||
|
||||
@@ -20,7 +22,7 @@ class CommentRepo
|
||||
/**
|
||||
* Create a new comment on an entity.
|
||||
*/
|
||||
public function create(Entity $entity, string $html, ?int $parent_id): Comment
|
||||
public function create(Entity $entity, string $html, ?int $parentId, string $contentRef): Comment
|
||||
{
|
||||
$userId = user()->id;
|
||||
$comment = new Comment();
|
||||
@@ -29,7 +31,8 @@ class CommentRepo
|
||||
$comment->created_by = $userId;
|
||||
$comment->updated_by = $userId;
|
||||
$comment->local_id = $this->getNextLocalId($entity);
|
||||
$comment->parent_id = $parent_id;
|
||||
$comment->parent_id = $parentId;
|
||||
$comment->content_ref = preg_match('/^bkmrk-(.*?):\d+:(\d*-\d*)?$/', $contentRef) === 1 ? $contentRef : '';
|
||||
|
||||
$entity->comments()->save($comment);
|
||||
ActivityService::add(ActivityType::COMMENT_CREATE, $comment);
|
||||
@@ -52,6 +55,41 @@ class CommentRepo
|
||||
return $comment;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Archive an existing comment.
|
||||
*/
|
||||
public function archive(Comment $comment): Comment
|
||||
{
|
||||
if ($comment->parent_id) {
|
||||
throw new NotifyException('Only top-level comments can be archived.', '/', 400);
|
||||
}
|
||||
|
||||
$comment->archived = true;
|
||||
$comment->save();
|
||||
|
||||
ActivityService::add(ActivityType::COMMENT_UPDATE, $comment);
|
||||
|
||||
return $comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Un-archive an existing comment.
|
||||
*/
|
||||
public function unarchive(Comment $comment): Comment
|
||||
{
|
||||
if ($comment->parent_id) {
|
||||
throw new NotifyException('Only top-level comments can be un-archived.', '/', 400);
|
||||
}
|
||||
|
||||
$comment->archived = false;
|
||||
$comment->save();
|
||||
|
||||
ActivityService::add(ActivityType::COMMENT_UPDATE, $comment);
|
||||
|
||||
return $comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a comment from the system.
|
||||
*/
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
namespace BookStack\Activity\Controllers;
|
||||
|
||||
use BookStack\Activity\CommentRepo;
|
||||
use BookStack\Activity\Tools\CommentTree;
|
||||
use BookStack\Activity\Tools\CommentTreeNode;
|
||||
use BookStack\Entities\Queries\PageQueries;
|
||||
use BookStack\Http\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -26,6 +28,7 @@ class CommentController extends Controller
|
||||
$input = $this->validate($request, [
|
||||
'html' => ['required', 'string'],
|
||||
'parent_id' => ['nullable', 'integer'],
|
||||
'content_ref' => ['string'],
|
||||
]);
|
||||
|
||||
$page = $this->pageQueries->findVisibleById($pageId);
|
||||
@@ -40,14 +43,12 @@ class CommentController extends Controller
|
||||
|
||||
// Create a new comment.
|
||||
$this->checkPermission('comment-create-all');
|
||||
$comment = $this->commentRepo->create($page, $input['html'], $input['parent_id'] ?? null);
|
||||
$contentRef = $input['content_ref'] ?? '';
|
||||
$comment = $this->commentRepo->create($page, $input['html'], $input['parent_id'] ?? null, $contentRef);
|
||||
|
||||
return view('comments.comment-branch', [
|
||||
'readOnly' => false,
|
||||
'branch' => [
|
||||
'comment' => $comment,
|
||||
'children' => [],
|
||||
]
|
||||
'branch' => new CommentTreeNode($comment, 0, []),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -74,6 +75,46 @@ class CommentController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a comment as archived.
|
||||
*/
|
||||
public function archive(int $id)
|
||||
{
|
||||
$comment = $this->commentRepo->getById($id);
|
||||
$this->checkOwnablePermission('page-view', $comment->entity);
|
||||
if (!userCan('comment-update', $comment) && !userCan('comment-delete', $comment)) {
|
||||
$this->showPermissionError();
|
||||
}
|
||||
|
||||
$this->commentRepo->archive($comment);
|
||||
|
||||
$tree = new CommentTree($comment->entity);
|
||||
return view('comments.comment-branch', [
|
||||
'readOnly' => false,
|
||||
'branch' => $tree->getCommentNodeForId($id),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmark a comment as archived.
|
||||
*/
|
||||
public function unarchive(int $id)
|
||||
{
|
||||
$comment = $this->commentRepo->getById($id);
|
||||
$this->checkOwnablePermission('page-view', $comment->entity);
|
||||
if (!userCan('comment-update', $comment) && !userCan('comment-delete', $comment)) {
|
||||
$this->showPermissionError();
|
||||
}
|
||||
|
||||
$this->commentRepo->unarchive($comment);
|
||||
|
||||
$tree = new CommentTree($comment->entity);
|
||||
return view('comments.comment-branch', [
|
||||
'readOnly' => false,
|
||||
'branch' => $tree->getCommentNodeForId($id),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a comment from the system.
|
||||
*/
|
||||
|
||||
@@ -19,6 +19,8 @@ use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
* @property int $entity_id
|
||||
* @property int $created_by
|
||||
* @property int $updated_by
|
||||
* @property string $content_ref
|
||||
* @property bool $archived
|
||||
*/
|
||||
class Comment extends Model implements Loggable
|
||||
{
|
||||
|
||||
@@ -12,6 +12,8 @@ use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string $value
|
||||
* @property int $entity_id
|
||||
* @property string $entity_type
|
||||
* @property int $order
|
||||
*/
|
||||
class Tag extends Model
|
||||
|
||||
@@ -9,7 +9,7 @@ class CommentTree
|
||||
{
|
||||
/**
|
||||
* The built nested tree structure array.
|
||||
* @var array{comment: Comment, depth: int, children: array}[]
|
||||
* @var CommentTreeNode[]
|
||||
*/
|
||||
protected array $tree;
|
||||
protected array $comments;
|
||||
@@ -28,7 +28,7 @@ class CommentTree
|
||||
|
||||
public function empty(): bool
|
||||
{
|
||||
return count($this->tree) === 0;
|
||||
return count($this->getActive()) === 0;
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
@@ -36,9 +36,35 @@ class CommentTree
|
||||
return count($this->comments);
|
||||
}
|
||||
|
||||
public function get(): array
|
||||
public function getActive(): array
|
||||
{
|
||||
return $this->tree;
|
||||
return array_filter($this->tree, fn (CommentTreeNode $node) => !$node->comment->archived);
|
||||
}
|
||||
|
||||
public function activeThreadCount(): int
|
||||
{
|
||||
return count($this->getActive());
|
||||
}
|
||||
|
||||
public function getArchived(): array
|
||||
{
|
||||
return array_filter($this->tree, fn (CommentTreeNode $node) => $node->comment->archived);
|
||||
}
|
||||
|
||||
public function archivedThreadCount(): int
|
||||
{
|
||||
return count($this->getArchived());
|
||||
}
|
||||
|
||||
public function getCommentNodeForId(int $commentId): ?CommentTreeNode
|
||||
{
|
||||
foreach ($this->tree as $node) {
|
||||
if ($node->comment->id === $commentId) {
|
||||
return $node;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function canUpdateAny(): bool
|
||||
@@ -54,6 +80,7 @@ class CommentTree
|
||||
|
||||
/**
|
||||
* @param Comment[] $comments
|
||||
* @return CommentTreeNode[]
|
||||
*/
|
||||
protected function createTree(array $comments): array
|
||||
{
|
||||
@@ -77,26 +104,22 @@ class CommentTree
|
||||
|
||||
$tree = [];
|
||||
foreach ($childMap[0] ?? [] as $childId) {
|
||||
$tree[] = $this->createTreeForId($childId, 0, $byId, $childMap);
|
||||
$tree[] = $this->createTreeNodeForId($childId, 0, $byId, $childMap);
|
||||
}
|
||||
|
||||
return $tree;
|
||||
}
|
||||
|
||||
protected function createTreeForId(int $id, int $depth, array &$byId, array &$childMap): array
|
||||
protected function createTreeNodeForId(int $id, int $depth, array &$byId, array &$childMap): CommentTreeNode
|
||||
{
|
||||
$childIds = $childMap[$id] ?? [];
|
||||
$children = [];
|
||||
|
||||
foreach ($childIds as $childId) {
|
||||
$children[] = $this->createTreeForId($childId, $depth + 1, $byId, $childMap);
|
||||
$children[] = $this->createTreeNodeForId($childId, $depth + 1, $byId, $childMap);
|
||||
}
|
||||
|
||||
return [
|
||||
'comment' => $byId[$id],
|
||||
'depth' => $depth,
|
||||
'children' => $children,
|
||||
];
|
||||
return new CommentTreeNode($byId[$id], $depth, $children);
|
||||
}
|
||||
|
||||
protected function loadComments(): array
|
||||
|
||||
23
app/Activity/Tools/CommentTreeNode.php
Normal file
23
app/Activity/Tools/CommentTreeNode.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Activity\Tools;
|
||||
|
||||
use BookStack\Activity\Models\Comment;
|
||||
|
||||
class CommentTreeNode
|
||||
{
|
||||
public Comment $comment;
|
||||
public int $depth;
|
||||
|
||||
/**
|
||||
* @var CommentTreeNode[]
|
||||
*/
|
||||
public array $children;
|
||||
|
||||
public function __construct(Comment $comment, int $depth, array $children)
|
||||
{
|
||||
$this->comment = $comment;
|
||||
$this->depth = $depth;
|
||||
$this->children = $children;
|
||||
}
|
||||
}
|
||||
@@ -3,17 +3,15 @@
|
||||
namespace BookStack\Activity\Tools;
|
||||
|
||||
use BookStack\Activity\Models\Tag;
|
||||
use BookStack\Entities\Models\BookChild;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Entities\Models\Page;
|
||||
|
||||
class TagClassGenerator
|
||||
{
|
||||
protected array $tags;
|
||||
|
||||
/**
|
||||
* @param Tag[] $tags
|
||||
*/
|
||||
public function __construct(array $tags)
|
||||
{
|
||||
$this->tags = $tags;
|
||||
public function __construct(
|
||||
protected Entity $entity
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -22,14 +20,23 @@ class TagClassGenerator
|
||||
public function generate(): array
|
||||
{
|
||||
$classes = [];
|
||||
$tags = $this->entity->tags->all();
|
||||
|
||||
foreach ($this->tags as $tag) {
|
||||
$name = $this->normalizeTagClassString($tag->name);
|
||||
$value = $this->normalizeTagClassString($tag->value);
|
||||
$classes[] = 'tag-name-' . $name;
|
||||
if ($value) {
|
||||
$classes[] = 'tag-value-' . $value;
|
||||
$classes[] = 'tag-pair-' . $name . '-' . $value;
|
||||
foreach ($tags as $tag) {
|
||||
array_push($classes, ...$this->generateClassesForTag($tag));
|
||||
}
|
||||
|
||||
if ($this->entity instanceof BookChild && userCan('view', $this->entity->book)) {
|
||||
$bookTags = $this->entity->book->tags;
|
||||
foreach ($bookTags as $bookTag) {
|
||||
array_push($classes, ...$this->generateClassesForTag($bookTag, 'book-'));
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->entity instanceof Page && $this->entity->chapter && userCan('view', $this->entity->chapter)) {
|
||||
$chapterTags = $this->entity->chapter->tags;
|
||||
foreach ($chapterTags as $chapterTag) {
|
||||
array_push($classes, ...$this->generateClassesForTag($chapterTag, 'chapter-'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +48,22 @@ class TagClassGenerator
|
||||
return implode(' ', $this->generate());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
protected function generateClassesForTag(Tag $tag, string $prefix = ''): array
|
||||
{
|
||||
$classes = [];
|
||||
$name = $this->normalizeTagClassString($tag->name);
|
||||
$value = $this->normalizeTagClassString($tag->value);
|
||||
$classes[] = "{$prefix}tag-name-{$name}";
|
||||
if ($value) {
|
||||
$classes[] = "{$prefix}tag-value-{$value}";
|
||||
$classes[] = "{$prefix}tag-pair-{$name}-{$value}";
|
||||
}
|
||||
return $classes;
|
||||
}
|
||||
|
||||
protected function normalizeTagClassString(string $value): string
|
||||
{
|
||||
$value = str_replace(' ', '', strtolower($value));
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace BookStack\Api;
|
||||
|
||||
use BookStack\App\AppVersion;
|
||||
use BookStack\Http\ApiController;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||
@@ -25,7 +26,7 @@ class ApiDocsGenerator
|
||||
*/
|
||||
public static function generateConsideringCache(): Collection
|
||||
{
|
||||
$appVersion = trim(file_get_contents(base_path('version')));
|
||||
$appVersion = AppVersion::get();
|
||||
$cacheKey = 'api-docs::' . $appVersion;
|
||||
$isProduction = config('app.env') === 'production';
|
||||
$cacheVal = $isProduction ? Cache::get($cacheKey) : null;
|
||||
|
||||
24
app/App/AppVersion.php
Normal file
24
app/App/AppVersion.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\App;
|
||||
|
||||
class AppVersion
|
||||
{
|
||||
protected static string $version = '';
|
||||
|
||||
/**
|
||||
* Get the application's version number from its top-level `version` text file.
|
||||
*/
|
||||
public static function get(): string
|
||||
{
|
||||
if (!empty(static::$version)) {
|
||||
return static::$version;
|
||||
}
|
||||
|
||||
$versionFile = base_path('version');
|
||||
$version = trim(file_get_contents($versionFile));
|
||||
static::$version = $version;
|
||||
|
||||
return $version;
|
||||
}
|
||||
}
|
||||
31
app/App/SystemApiController.php
Normal file
31
app/App/SystemApiController.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\App;
|
||||
|
||||
use BookStack\Http\ApiController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class SystemApiController extends ApiController
|
||||
{
|
||||
/**
|
||||
* Read details regarding the BookStack instance.
|
||||
* Some details may be null where not set, like the app logo for example.
|
||||
*/
|
||||
public function read(): JsonResponse
|
||||
{
|
||||
$logoSetting = setting('app-logo', '');
|
||||
if ($logoSetting === 'none') {
|
||||
$logo = null;
|
||||
} else {
|
||||
$logo = $logoSetting ? url($logoSetting) : url('/logo.png');
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'version' => AppVersion::get(),
|
||||
'instance_id' => setting('instance-id'),
|
||||
'app_name' => setting('app-name'),
|
||||
'app_logo' => $logo,
|
||||
'base_url' => url('/'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use BookStack\App\AppVersion;
|
||||
use BookStack\App\Model;
|
||||
use BookStack\Facades\Theme;
|
||||
use BookStack\Permissions\PermissionApplicator;
|
||||
@@ -13,12 +14,7 @@ use BookStack\Users\Models\User;
|
||||
*/
|
||||
function versioned_asset(string $file = ''): string
|
||||
{
|
||||
static $version = null;
|
||||
|
||||
if (is_null($version)) {
|
||||
$versionFile = base_path('version');
|
||||
$version = trim(file_get_contents($versionFile));
|
||||
}
|
||||
$version = AppVersion::get();
|
||||
|
||||
$additional = '';
|
||||
if (config('app.env') === 'development') {
|
||||
|
||||
@@ -40,12 +40,16 @@ if (env('REDIS_SERVERS', false)) {
|
||||
|
||||
// MYSQL
|
||||
// Split out port from host if set
|
||||
$mysql_host = env('DB_HOST', 'localhost');
|
||||
$mysql_host_exploded = explode(':', $mysql_host);
|
||||
$mysql_port = env('DB_PORT', 3306);
|
||||
if (count($mysql_host_exploded) > 1) {
|
||||
$mysql_host = $mysql_host_exploded[0];
|
||||
$mysql_port = intval($mysql_host_exploded[1]);
|
||||
$mysqlHost = env('DB_HOST', 'localhost');
|
||||
$mysqlHostExploded = explode(':', $mysqlHost);
|
||||
$mysqlPort = env('DB_PORT', 3306);
|
||||
$mysqlHostIpv6 = str_starts_with($mysqlHost, '[');
|
||||
if ($mysqlHostIpv6 && str_contains($mysqlHost, ']:')) {
|
||||
$mysqlHost = implode(':', array_slice($mysqlHostExploded, 0, -1));
|
||||
$mysqlPort = intval(end($mysqlHostExploded));
|
||||
} else if (!$mysqlHostIpv6 && count($mysqlHostExploded) > 1) {
|
||||
$mysqlHost = $mysqlHostExploded[0];
|
||||
$mysqlPort = intval($mysqlHostExploded[1]);
|
||||
}
|
||||
|
||||
return [
|
||||
@@ -61,12 +65,12 @@ return [
|
||||
'mysql' => [
|
||||
'driver' => 'mysql',
|
||||
'url' => env('DATABASE_URL'),
|
||||
'host' => $mysql_host,
|
||||
'host' => $mysqlHost,
|
||||
'database' => env('DB_DATABASE', 'forge'),
|
||||
'username' => env('DB_USERNAME', 'forge'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'unix_socket' => env('DB_SOCKET', ''),
|
||||
'port' => $mysql_port,
|
||||
'port' => $mysqlPort,
|
||||
'charset' => 'utf8mb4',
|
||||
'collation' => 'utf8mb4_unicode_ci',
|
||||
// Prefixes are only semi-supported and may be unstable
|
||||
@@ -88,7 +92,7 @@ return [
|
||||
'database' => 'bookstack-test',
|
||||
'username' => env('MYSQL_USER', 'bookstack-test'),
|
||||
'password' => env('MYSQL_PASSWORD', 'bookstack-test'),
|
||||
'port' => $mysql_port,
|
||||
'port' => $mysqlPort,
|
||||
'charset' => 'utf8mb4',
|
||||
'collation' => 'utf8mb4_unicode_ci',
|
||||
'prefix' => '',
|
||||
|
||||
@@ -32,9 +32,9 @@ return [
|
||||
'local' => [
|
||||
'driver' => 'local',
|
||||
'root' => public_path(),
|
||||
'visibility' => 'public',
|
||||
'serve' => false,
|
||||
'throw' => true,
|
||||
'directory_visibility' => 'public',
|
||||
],
|
||||
|
||||
'local_secure_attachments' => [
|
||||
@@ -47,7 +47,6 @@ return [
|
||||
'local_secure_images' => [
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('uploads/images/'),
|
||||
'visibility' => 'public',
|
||||
'serve' => false,
|
||||
'throw' => true,
|
||||
],
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
// Configured mail encryption method.
|
||||
// STARTTLS should still be attempted, but tls/ssl forces TLS usage.
|
||||
$mailEncryption = env('MAIL_ENCRYPTION', null);
|
||||
$mailPort = intval(env('MAIL_PORT', 587));
|
||||
|
||||
return [
|
||||
|
||||
@@ -33,13 +34,13 @@ return [
|
||||
'transport' => 'smtp',
|
||||
'scheme' => null,
|
||||
'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
|
||||
'port' => env('MAIL_PORT', 587),
|
||||
'port' => $mailPort,
|
||||
'username' => env('MAIL_USERNAME'),
|
||||
'password' => env('MAIL_PASSWORD'),
|
||||
'verify_peer' => env('MAIL_VERIFY_SSL', true),
|
||||
'timeout' => null,
|
||||
'local_domain' => null,
|
||||
'tls_required' => ($mailEncryption === 'tls' || $mailEncryption === 'ssl'),
|
||||
'require_tls' => ($mailEncryption === 'tls' || $mailEncryption === 'ssl' || $mailPort === 465),
|
||||
],
|
||||
|
||||
'sendmail' => [
|
||||
|
||||
@@ -47,6 +47,12 @@ return [
|
||||
// Multiple values can be provided comma seperated.
|
||||
'additional_scopes' => env('OIDC_ADDITIONAL_SCOPES', null),
|
||||
|
||||
// Enable fetching of the user's avatar from the 'picture' claim on login.
|
||||
// Will only be fetched if the user doesn't already have an avatar image assigned.
|
||||
// This can be a security risk due to performing server-side fetching (with up to 3 redirects) of
|
||||
// data from external URLs. Only enable if you trust the OIDC auth provider to provide safe URLs for user images.
|
||||
'fetch_avatar' => env('OIDC_FETCH_AVATAR', false),
|
||||
|
||||
// Group sync options
|
||||
// Enable syncing, upon login, of OIDC groups to BookStack roles
|
||||
'user_to_groups' => env('OIDC_USER_TO_GROUPS', false),
|
||||
|
||||
@@ -18,6 +18,7 @@ use BookStack\Exceptions\NotFoundException;
|
||||
use BookStack\Facades\Activity;
|
||||
use BookStack\Http\Controller;
|
||||
use BookStack\References\ReferenceFetcher;
|
||||
use BookStack\Util\DatabaseTransaction;
|
||||
use BookStack\Util\SimpleListOptions;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
@@ -263,7 +264,9 @@ class BookController extends Controller
|
||||
$this->checkPermission('bookshelf-create-all');
|
||||
$this->checkPermission('book-create-all');
|
||||
|
||||
$shelf = $transformer->transformBookToShelf($book);
|
||||
$shelf = (new DatabaseTransaction(function () use ($book, $transformer) {
|
||||
return $transformer->transformBookToShelf($book);
|
||||
}))->run();
|
||||
|
||||
return redirect($shelf->getUrl());
|
||||
}
|
||||
|
||||
@@ -9,12 +9,11 @@ use BookStack\Entities\Repos\ChapterRepo;
|
||||
use BookStack\Exceptions\PermissionsException;
|
||||
use BookStack\Http\ApiController;
|
||||
use Exception;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ChapterApiController extends ApiController
|
||||
{
|
||||
protected $rules = [
|
||||
protected array $rules = [
|
||||
'create' => [
|
||||
'book_id' => ['required', 'integer'],
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
|
||||
@@ -18,6 +18,7 @@ use BookStack\Exceptions\NotifyException;
|
||||
use BookStack\Exceptions\PermissionsException;
|
||||
use BookStack\Http\Controller;
|
||||
use BookStack\References\ReferenceFetcher;
|
||||
use BookStack\Util\DatabaseTransaction;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Throwable;
|
||||
@@ -269,7 +270,9 @@ class ChapterController extends Controller
|
||||
$this->checkOwnablePermission('chapter-delete', $chapter);
|
||||
$this->checkPermission('book-create-all');
|
||||
|
||||
$book = $transformer->transformChapterToBook($chapter);
|
||||
$book = (new DatabaseTransaction(function () use ($chapter, $transformer) {
|
||||
return $transformer->transformChapterToBook($chapter);
|
||||
}))->run();
|
||||
|
||||
return redirect($book->getUrl());
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ use Illuminate\Http\Request;
|
||||
|
||||
class PageApiController extends ApiController
|
||||
{
|
||||
protected $rules = [
|
||||
protected array $rules = [
|
||||
'create' => [
|
||||
'book_id' => ['required_without:chapter_id', 'integer'],
|
||||
'chapter_id' => ['required_without:book_id', 'integer'],
|
||||
|
||||
@@ -17,6 +17,7 @@ use BookStack\Entities\Tools\PageContent;
|
||||
use BookStack\Entities\Tools\PageEditActivity;
|
||||
use BookStack\Entities\Tools\PageEditorData;
|
||||
use BookStack\Exceptions\NotFoundException;
|
||||
use BookStack\Exceptions\NotifyException;
|
||||
use BookStack\Exceptions\PermissionsException;
|
||||
use BookStack\Http\Controller;
|
||||
use BookStack\References\ReferenceFetcher;
|
||||
@@ -196,7 +197,7 @@ class PageController extends Controller
|
||||
public function edit(Request $request, string $bookSlug, string $pageSlug)
|
||||
{
|
||||
$page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
|
||||
$this->checkOwnablePermission('page-update', $page);
|
||||
$this->checkOwnablePermission('page-update', $page, $page->getUrl());
|
||||
|
||||
$editorData = new PageEditorData($page, $this->entityQueries, $request->query('editor', ''));
|
||||
if ($editorData->getWarnings()) {
|
||||
|
||||
@@ -43,7 +43,6 @@ class PageRevisionController extends Controller
|
||||
->selectRaw("IF(markdown = '', false, true) as is_markdown")
|
||||
->with(['page.book', 'createdBy'])
|
||||
->reorder('id', $listOptions->getOrder())
|
||||
->reorder('created_at', $listOptions->getOrder())
|
||||
->paginate(50);
|
||||
|
||||
$this->setPageTitle(trans('entities.pages_revisions_named', ['pageName' => $page->getShortName()]));
|
||||
@@ -52,6 +51,7 @@ class PageRevisionController extends Controller
|
||||
'revisions' => $revisions,
|
||||
'page' => $page,
|
||||
'listOptions' => $listOptions,
|
||||
'oldestRevisionId' => $page->revisions()->min('id'),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -77,7 +77,6 @@ class BaseRepo
|
||||
$entity->touch();
|
||||
}
|
||||
|
||||
$entity->rebuildPermissions();
|
||||
$entity->indexForSearch();
|
||||
$this->referenceStore->updateForEntity($entity);
|
||||
|
||||
@@ -139,7 +138,7 @@ class BaseRepo
|
||||
|
||||
/**
|
||||
* Sort the parent of the given entity, if any auto sort actions are set for it.
|
||||
* Typical ran during create/update/insert events.
|
||||
* Typically ran during create/update/insert events.
|
||||
*/
|
||||
public function sortParent(Entity $entity): void
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@ use BookStack\Exceptions\ImageUploadException;
|
||||
use BookStack\Facades\Activity;
|
||||
use BookStack\Sorting\SortRule;
|
||||
use BookStack\Uploads\ImageRepo;
|
||||
use BookStack\Util\DatabaseTransaction;
|
||||
use Exception;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
|
||||
@@ -28,19 +29,22 @@ class BookRepo
|
||||
*/
|
||||
public function create(array $input): Book
|
||||
{
|
||||
$book = new Book();
|
||||
$this->baseRepo->create($book, $input);
|
||||
$this->baseRepo->updateCoverImage($book, $input['image'] ?? null);
|
||||
$this->baseRepo->updateDefaultTemplate($book, intval($input['default_template_id'] ?? null));
|
||||
Activity::add(ActivityType::BOOK_CREATE, $book);
|
||||
return (new DatabaseTransaction(function () use ($input) {
|
||||
$book = new Book();
|
||||
|
||||
$defaultBookSortSetting = intval(setting('sorting-book-default', '0'));
|
||||
if ($defaultBookSortSetting && SortRule::query()->find($defaultBookSortSetting)) {
|
||||
$book->sort_rule_id = $defaultBookSortSetting;
|
||||
$book->save();
|
||||
}
|
||||
$this->baseRepo->create($book, $input);
|
||||
$this->baseRepo->updateCoverImage($book, $input['image'] ?? null);
|
||||
$this->baseRepo->updateDefaultTemplate($book, intval($input['default_template_id'] ?? null));
|
||||
Activity::add(ActivityType::BOOK_CREATE, $book);
|
||||
|
||||
return $book;
|
||||
$defaultBookSortSetting = intval(setting('sorting-book-default', '0'));
|
||||
if ($defaultBookSortSetting && SortRule::query()->find($defaultBookSortSetting)) {
|
||||
$book->sort_rule_id = $defaultBookSortSetting;
|
||||
$book->save();
|
||||
}
|
||||
|
||||
return $book;
|
||||
}))->run();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,6 +7,7 @@ use BookStack\Entities\Models\Bookshelf;
|
||||
use BookStack\Entities\Queries\BookQueries;
|
||||
use BookStack\Entities\Tools\TrashCan;
|
||||
use BookStack\Facades\Activity;
|
||||
use BookStack\Util\DatabaseTransaction;
|
||||
use Exception;
|
||||
|
||||
class BookshelfRepo
|
||||
@@ -23,13 +24,14 @@ class BookshelfRepo
|
||||
*/
|
||||
public function create(array $input, array $bookIds): Bookshelf
|
||||
{
|
||||
$shelf = new Bookshelf();
|
||||
$this->baseRepo->create($shelf, $input);
|
||||
$this->baseRepo->updateCoverImage($shelf, $input['image'] ?? null);
|
||||
$this->updateBooks($shelf, $bookIds);
|
||||
Activity::add(ActivityType::BOOKSHELF_CREATE, $shelf);
|
||||
|
||||
return $shelf;
|
||||
return (new DatabaseTransaction(function () use ($input, $bookIds) {
|
||||
$shelf = new Bookshelf();
|
||||
$this->baseRepo->create($shelf, $input);
|
||||
$this->baseRepo->updateCoverImage($shelf, $input['image'] ?? null);
|
||||
$this->updateBooks($shelf, $bookIds);
|
||||
Activity::add(ActivityType::BOOKSHELF_CREATE, $shelf);
|
||||
return $shelf;
|
||||
}))->run();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,6 +11,7 @@ use BookStack\Entities\Tools\TrashCan;
|
||||
use BookStack\Exceptions\MoveOperationException;
|
||||
use BookStack\Exceptions\PermissionsException;
|
||||
use BookStack\Facades\Activity;
|
||||
use BookStack\Util\DatabaseTransaction;
|
||||
use Exception;
|
||||
|
||||
class ChapterRepo
|
||||
@@ -27,16 +28,18 @@ class ChapterRepo
|
||||
*/
|
||||
public function create(array $input, Book $parentBook): Chapter
|
||||
{
|
||||
$chapter = new Chapter();
|
||||
$chapter->book_id = $parentBook->id;
|
||||
$chapter->priority = (new BookContents($parentBook))->getLastPriority() + 1;
|
||||
$this->baseRepo->create($chapter, $input);
|
||||
$this->baseRepo->updateDefaultTemplate($chapter, intval($input['default_template_id'] ?? null));
|
||||
Activity::add(ActivityType::CHAPTER_CREATE, $chapter);
|
||||
return (new DatabaseTransaction(function () use ($input, $parentBook) {
|
||||
$chapter = new Chapter();
|
||||
$chapter->book_id = $parentBook->id;
|
||||
$chapter->priority = (new BookContents($parentBook))->getLastPriority() + 1;
|
||||
$this->baseRepo->create($chapter, $input);
|
||||
$this->baseRepo->updateDefaultTemplate($chapter, intval($input['default_template_id'] ?? null));
|
||||
Activity::add(ActivityType::CHAPTER_CREATE, $chapter);
|
||||
|
||||
$this->baseRepo->sortParent($chapter);
|
||||
$this->baseRepo->sortParent($chapter);
|
||||
|
||||
return $chapter;
|
||||
return $chapter;
|
||||
}))->run();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,12 +91,14 @@ class ChapterRepo
|
||||
throw new PermissionsException('User does not have permission to create a chapter within the chosen book');
|
||||
}
|
||||
|
||||
$chapter->changeBook($parent->id);
|
||||
$chapter->rebuildPermissions();
|
||||
Activity::add(ActivityType::CHAPTER_MOVE, $chapter);
|
||||
return (new DatabaseTransaction(function () use ($chapter, $parent) {
|
||||
$chapter->changeBook($parent->id);
|
||||
$chapter->rebuildPermissions();
|
||||
Activity::add(ActivityType::CHAPTER_MOVE, $chapter);
|
||||
|
||||
$this->baseRepo->sortParent($chapter);
|
||||
$this->baseRepo->sortParent($chapter);
|
||||
|
||||
return $parent;
|
||||
return $parent;
|
||||
}))->run();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ use BookStack\Exceptions\PermissionsException;
|
||||
use BookStack\Facades\Activity;
|
||||
use BookStack\References\ReferenceStore;
|
||||
use BookStack\References\ReferenceUpdater;
|
||||
use BookStack\Util\DatabaseTransaction;
|
||||
use Exception;
|
||||
|
||||
class PageRepo
|
||||
@@ -61,8 +62,10 @@ class PageRepo
|
||||
]);
|
||||
}
|
||||
|
||||
$page->save();
|
||||
$page->refresh()->rebuildPermissions();
|
||||
(new DatabaseTransaction(function () use ($page) {
|
||||
$page->save();
|
||||
$page->refresh()->rebuildPermissions();
|
||||
}))->run();
|
||||
|
||||
return $page;
|
||||
}
|
||||
@@ -72,26 +75,29 @@ class PageRepo
|
||||
*/
|
||||
public function publishDraft(Page $draft, array $input): Page
|
||||
{
|
||||
$draft->draft = false;
|
||||
$draft->revision_count = 1;
|
||||
$draft->priority = $this->getNewPriority($draft);
|
||||
$this->updateTemplateStatusAndContentFromInput($draft, $input);
|
||||
$this->baseRepo->update($draft, $input);
|
||||
return (new DatabaseTransaction(function () use ($draft, $input) {
|
||||
$draft->draft = false;
|
||||
$draft->revision_count = 1;
|
||||
$draft->priority = $this->getNewPriority($draft);
|
||||
$this->updateTemplateStatusAndContentFromInput($draft, $input);
|
||||
$this->baseRepo->update($draft, $input);
|
||||
$draft->rebuildPermissions();
|
||||
|
||||
$summary = trim($input['summary'] ?? '') ?: trans('entities.pages_initial_revision');
|
||||
$this->revisionRepo->storeNewForPage($draft, $summary);
|
||||
$draft->refresh();
|
||||
$summary = trim($input['summary'] ?? '') ?: trans('entities.pages_initial_revision');
|
||||
$this->revisionRepo->storeNewForPage($draft, $summary);
|
||||
$draft->refresh();
|
||||
|
||||
Activity::add(ActivityType::PAGE_CREATE, $draft);
|
||||
$this->baseRepo->sortParent($draft);
|
||||
Activity::add(ActivityType::PAGE_CREATE, $draft);
|
||||
$this->baseRepo->sortParent($draft);
|
||||
|
||||
return $draft;
|
||||
return $draft;
|
||||
}))->run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Directly update the content for the given page from the provided input.
|
||||
* Used for direct content access in a way that performs required changes
|
||||
* (Search index & reference regen) without performing an official update.
|
||||
* (Search index and reference regen) without performing an official update.
|
||||
*/
|
||||
public function setContentFromInput(Page $page, array $input): void
|
||||
{
|
||||
@@ -116,7 +122,7 @@ class PageRepo
|
||||
$page->revision_count++;
|
||||
$page->save();
|
||||
|
||||
// Remove all update drafts for this user & page.
|
||||
// Remove all update drafts for this user and page.
|
||||
$this->revisionRepo->deleteDraftsForCurrentUser($page);
|
||||
|
||||
// Save a revision after updating
|
||||
@@ -269,16 +275,18 @@ class PageRepo
|
||||
throw new PermissionsException('User does not have permission to create a page within the new parent');
|
||||
}
|
||||
|
||||
$page->chapter_id = ($parent instanceof Chapter) ? $parent->id : null;
|
||||
$newBookId = ($parent instanceof Chapter) ? $parent->book->id : $parent->id;
|
||||
$page->changeBook($newBookId);
|
||||
$page->rebuildPermissions();
|
||||
return (new DatabaseTransaction(function () use ($page, $parent) {
|
||||
$page->chapter_id = ($parent instanceof Chapter) ? $parent->id : null;
|
||||
$newBookId = ($parent instanceof Chapter) ? $parent->book->id : $parent->id;
|
||||
$page->changeBook($newBookId);
|
||||
$page->rebuildPermissions();
|
||||
|
||||
Activity::add(ActivityType::PAGE_MOVE, $page);
|
||||
Activity::add(ActivityType::PAGE_MOVE, $page);
|
||||
|
||||
$this->baseRepo->sortParent($page);
|
||||
$this->baseRepo->sortParent($page);
|
||||
|
||||
return $parent;
|
||||
return $parent;
|
||||
}))->run();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,17 +13,12 @@ use BookStack\Facades\Activity;
|
||||
|
||||
class HierarchyTransformer
|
||||
{
|
||||
protected BookRepo $bookRepo;
|
||||
protected BookshelfRepo $shelfRepo;
|
||||
protected Cloner $cloner;
|
||||
protected TrashCan $trashCan;
|
||||
|
||||
public function __construct(BookRepo $bookRepo, BookshelfRepo $shelfRepo, Cloner $cloner, TrashCan $trashCan)
|
||||
{
|
||||
$this->bookRepo = $bookRepo;
|
||||
$this->shelfRepo = $shelfRepo;
|
||||
$this->cloner = $cloner;
|
||||
$this->trashCan = $trashCan;
|
||||
public function __construct(
|
||||
protected BookRepo $bookRepo,
|
||||
protected BookshelfRepo $shelfRepo,
|
||||
protected Cloner $cloner,
|
||||
protected TrashCan $trashCan
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,6 +15,7 @@ use BookStack\Exceptions\NotifyException;
|
||||
use BookStack\Facades\Activity;
|
||||
use BookStack\Uploads\AttachmentService;
|
||||
use BookStack\Uploads\ImageService;
|
||||
use BookStack\Util\DatabaseTransaction;
|
||||
use Exception;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Carbon;
|
||||
@@ -357,25 +358,26 @@ class TrashCan
|
||||
|
||||
/**
|
||||
* Destroy the given entity.
|
||||
* Returns the number of total entities destroyed in the operation.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function destroyEntity(Entity $entity): int
|
||||
{
|
||||
if ($entity instanceof Page) {
|
||||
return $this->destroyPage($entity);
|
||||
}
|
||||
if ($entity instanceof Chapter) {
|
||||
return $this->destroyChapter($entity);
|
||||
}
|
||||
if ($entity instanceof Book) {
|
||||
return $this->destroyBook($entity);
|
||||
}
|
||||
if ($entity instanceof Bookshelf) {
|
||||
return $this->destroyShelf($entity);
|
||||
}
|
||||
$result = (new DatabaseTransaction(function () use ($entity) {
|
||||
if ($entity instanceof Page) {
|
||||
return $this->destroyPage($entity);
|
||||
} else if ($entity instanceof Chapter) {
|
||||
return $this->destroyChapter($entity);
|
||||
} else if ($entity instanceof Book) {
|
||||
return $this->destroyBook($entity);
|
||||
} else if ($entity instanceof Bookshelf) {
|
||||
return $this->destroyShelf($entity);
|
||||
}
|
||||
return null;
|
||||
}))->run();
|
||||
|
||||
return 0;
|
||||
return $result ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace BookStack\Exceptions;
|
||||
|
||||
use BookStack\App\AppVersion;
|
||||
use Illuminate\Contracts\Foundation\ExceptionRenderer;
|
||||
|
||||
class BookStackExceptionHandlerPage implements ExceptionRenderer
|
||||
@@ -30,9 +31,7 @@ class BookStackExceptionHandlerPage implements ExceptionRenderer
|
||||
return [
|
||||
'PHP Version' => phpversion(),
|
||||
'BookStack Version' => $this->safeReturn(function () {
|
||||
$versionFile = base_path('version');
|
||||
|
||||
return trim(file_get_contents($versionFile));
|
||||
return AppVersion::get();
|
||||
}, 'unknown'),
|
||||
'Theme Configured' => $this->safeReturn(function () {
|
||||
return config('view.theme');
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace BookStack\Exports\Controllers;
|
||||
|
||||
use BookStack\Entities\Queries\BookQueries;
|
||||
use BookStack\Exports\ExportFormatter;
|
||||
use BookStack\Exports\ZipExports\ZipExportBuilder;
|
||||
use BookStack\Http\ApiController;
|
||||
use Throwable;
|
||||
|
||||
@@ -63,4 +64,15 @@ class BookExportApiController extends ApiController
|
||||
|
||||
return $this->download()->directly($markdown, $book->slug . '.md');
|
||||
}
|
||||
|
||||
/**
|
||||
* Export a book as a contained ZIP export file.
|
||||
*/
|
||||
public function exportZip(int $id, ZipExportBuilder $builder)
|
||||
{
|
||||
$book = $this->queries->findVisibleByIdOrFail($id);
|
||||
$zip = $builder->buildForBook($book);
|
||||
|
||||
return $this->download()->streamedFileDirectly($zip, $book->slug . '.zip', true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace BookStack\Exports\Controllers;
|
||||
|
||||
use BookStack\Entities\Queries\ChapterQueries;
|
||||
use BookStack\Exports\ExportFormatter;
|
||||
use BookStack\Exports\ZipExports\ZipExportBuilder;
|
||||
use BookStack\Http\ApiController;
|
||||
use Throwable;
|
||||
|
||||
@@ -63,4 +64,15 @@ class ChapterExportApiController extends ApiController
|
||||
|
||||
return $this->download()->directly($markdown, $chapter->slug . '.md');
|
||||
}
|
||||
|
||||
/**
|
||||
* Export a chapter as a contained ZIP file.
|
||||
*/
|
||||
public function exportZip(int $id, ZipExportBuilder $builder)
|
||||
{
|
||||
$chapter = $this->queries->findVisibleByIdOrFail($id);
|
||||
$zip = $builder->buildForChapter($chapter);
|
||||
|
||||
return $this->download()->streamedFileDirectly($zip, $chapter->slug . '.zip', true);
|
||||
}
|
||||
}
|
||||
|
||||
144
app/Exports/Controllers/ImportApiController.php
Normal file
144
app/Exports/Controllers/ImportApiController.php
Normal file
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BookStack\Exports\Controllers;
|
||||
|
||||
use BookStack\Exceptions\ZipImportException;
|
||||
use BookStack\Exceptions\ZipValidationException;
|
||||
use BookStack\Exports\ImportRepo;
|
||||
use BookStack\Http\ApiController;
|
||||
use BookStack\Uploads\AttachmentService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
class ImportApiController extends ApiController
|
||||
{
|
||||
public function __construct(
|
||||
protected ImportRepo $imports,
|
||||
) {
|
||||
$this->middleware('can:content-import');
|
||||
}
|
||||
|
||||
/**
|
||||
* List existing ZIP imports visible to the user.
|
||||
* Requires permission to import content.
|
||||
*/
|
||||
public function list(): JsonResponse
|
||||
{
|
||||
$query = $this->imports->queryVisible();
|
||||
|
||||
return $this->apiListingResponse($query, [
|
||||
'id', 'name', 'size', 'type', 'created_by', 'created_at', 'updated_at'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a new import from a ZIP file.
|
||||
* This does not actually run the import since that is performed via the "run" endpoint.
|
||||
* This uploads, validates and stores the ZIP file so it's ready to be imported.
|
||||
*
|
||||
* This "file" parameter must be a BookStack-compatible ZIP file, and this must be
|
||||
* sent via a 'multipart/form-data' type request.
|
||||
*
|
||||
* Requires permission to import content.
|
||||
*/
|
||||
public function create(Request $request): JsonResponse
|
||||
{
|
||||
$this->validate($request, $this->rules()['create']);
|
||||
|
||||
$file = $request->file('file');
|
||||
|
||||
try {
|
||||
$import = $this->imports->storeFromUpload($file);
|
||||
} catch (ZipValidationException $exception) {
|
||||
$message = "ZIP upload failed with the following validation errors: \n" . $this->formatErrors($exception->errors);
|
||||
return $this->jsonError($message, 422);
|
||||
}
|
||||
|
||||
return response()->json($import);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read details of a pending ZIP import.
|
||||
* The "details" property contains high-level metadata regarding the ZIP import content,
|
||||
* and the structure of this will change depending on import "type".
|
||||
* Requires permission to import content.
|
||||
*/
|
||||
public function read(int $id): JsonResponse
|
||||
{
|
||||
$import = $this->imports->findVisible($id);
|
||||
|
||||
$import->setAttribute('details', $import->decodeMetadata());
|
||||
|
||||
return response()->json($import);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the import process for an uploaded ZIP import.
|
||||
* The "parent_id" and "parent_type" parameters are required when the import type is "chapter" or "page".
|
||||
* On success, this endpoint returns the imported item.
|
||||
* Requires permission to import content.
|
||||
*/
|
||||
public function run(int $id, Request $request): JsonResponse
|
||||
{
|
||||
$import = $this->imports->findVisible($id);
|
||||
$parent = null;
|
||||
$rules = $this->rules()['run'];
|
||||
|
||||
if ($import->type === 'page' || $import->type === 'chapter') {
|
||||
$rules['parent_type'][] = 'required';
|
||||
$rules['parent_id'][] = 'required';
|
||||
$data = $this->validate($request, $rules);
|
||||
$parent = "{$data['parent_type']}:{$data['parent_id']}";
|
||||
}
|
||||
|
||||
try {
|
||||
$entity = $this->imports->runImport($import, $parent);
|
||||
} catch (ZipImportException $exception) {
|
||||
$message = "ZIP import failed with the following errors: \n" . $this->formatErrors($exception->errors);
|
||||
return $this->jsonError($message);
|
||||
}
|
||||
|
||||
return response()->json($entity->withoutRelations());
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a pending ZIP import from the system.
|
||||
* Requires permission to import content.
|
||||
*/
|
||||
public function delete(int $id): Response
|
||||
{
|
||||
$import = $this->imports->findVisible($id);
|
||||
$this->imports->deleteImport($import);
|
||||
|
||||
return response('', 204);
|
||||
}
|
||||
|
||||
protected function rules(): array
|
||||
{
|
||||
return [
|
||||
'create' => [
|
||||
'file' => ['required', ...AttachmentService::getFileValidationRules()],
|
||||
],
|
||||
'run' => [
|
||||
'parent_type' => ['string', 'in:book,chapter'],
|
||||
'parent_id' => ['int'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
protected function formatErrors(array $errors): string
|
||||
{
|
||||
$parts = [];
|
||||
foreach ($errors as $key => $error) {
|
||||
if (is_string($key)) {
|
||||
$parts[] = "[{$key}] {$error}";
|
||||
} else {
|
||||
$parts[] = $error;
|
||||
}
|
||||
}
|
||||
return implode("\n", $parts);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ namespace BookStack\Exports\Controllers;
|
||||
|
||||
use BookStack\Entities\Queries\PageQueries;
|
||||
use BookStack\Exports\ExportFormatter;
|
||||
use BookStack\Exports\ZipExports\ZipExportBuilder;
|
||||
use BookStack\Http\ApiController;
|
||||
use Throwable;
|
||||
|
||||
@@ -63,4 +64,15 @@ class PageExportApiController extends ApiController
|
||||
|
||||
return $this->download()->directly($markdown, $page->slug . '.md');
|
||||
}
|
||||
|
||||
/**
|
||||
* Export a page as a contained ZIP file.
|
||||
*/
|
||||
public function exportZip(int $id, ZipExportBuilder $builder)
|
||||
{
|
||||
$page = $this->queries->findVisibleByIdOrFail($id);
|
||||
$zip = $builder->buildForPage($page);
|
||||
|
||||
return $this->download()->streamedFileDirectly($zip, $page->slug . '.zip', true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ class Import extends Model implements Loggable
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $hidden = ['metadata'];
|
||||
|
||||
public function getSizeString(): string
|
||||
{
|
||||
$mb = round($this->size / 1000000, 2);
|
||||
|
||||
@@ -17,6 +17,7 @@ use BookStack\Exports\ZipExports\ZipExportValidator;
|
||||
use BookStack\Exports\ZipExports\ZipImportRunner;
|
||||
use BookStack\Facades\Activity;
|
||||
use BookStack\Uploads\FileStorage;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
@@ -34,6 +35,11 @@ class ImportRepo
|
||||
* @return Collection<Import>
|
||||
*/
|
||||
public function getVisibleImports(): Collection
|
||||
{
|
||||
return $this->queryVisible()->get();
|
||||
}
|
||||
|
||||
public function queryVisible(): Builder
|
||||
{
|
||||
$query = Import::query();
|
||||
|
||||
@@ -41,7 +47,7 @@ class ImportRepo
|
||||
$query->where('created_by', user()->id);
|
||||
}
|
||||
|
||||
return $query->get();
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function findVisible(int $id): Import
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace BookStack\Exports\ZipExports;
|
||||
|
||||
use BookStack\App\AppVersion;
|
||||
use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Models\Chapter;
|
||||
use BookStack\Entities\Models\Page;
|
||||
@@ -70,7 +71,7 @@ class ZipExportBuilder
|
||||
$this->data['exported_at'] = date(DATE_ATOM);
|
||||
$this->data['instance'] = [
|
||||
'id' => setting('instance-id', ''),
|
||||
'version' => trim(file_get_contents(base_path('version'))),
|
||||
'version' => AppVersion::get(),
|
||||
];
|
||||
|
||||
$zipFile = tempnam(sys_get_temp_dir(), 'bszip-');
|
||||
|
||||
@@ -8,7 +8,7 @@ use Illuminate\Http\JsonResponse;
|
||||
|
||||
abstract class ApiController extends Controller
|
||||
{
|
||||
protected $rules = [];
|
||||
protected array $rules = [];
|
||||
|
||||
/**
|
||||
* Provide a paginated listing JSON response in a standard format
|
||||
|
||||
@@ -49,13 +49,13 @@ abstract class Controller extends BaseController
|
||||
* On a permission error redirect to home and display.
|
||||
* the error as a notification.
|
||||
*
|
||||
* @return never
|
||||
* @throws NotifyException
|
||||
*/
|
||||
protected function showPermissionError()
|
||||
protected function showPermissionError(string $redirectLocation = '/'): never
|
||||
{
|
||||
$message = request()->wantsJson() ? trans('errors.permissionJson') : trans('errors.permission');
|
||||
|
||||
throw new NotifyException($message, '/', 403);
|
||||
throw new NotifyException($message, $redirectLocation, 403);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,10 +81,10 @@ abstract class Controller extends BaseController
|
||||
/**
|
||||
* Check the current user's permissions against an ownable item otherwise throw an exception.
|
||||
*/
|
||||
protected function checkOwnablePermission(string $permission, Model $ownable): void
|
||||
protected function checkOwnablePermission(string $permission, Model $ownable, string $redirectLocation = '/'): void
|
||||
{
|
||||
if (!userCan($permission, $ownable)) {
|
||||
$this->showPermissionError();
|
||||
$this->showPermissionError($redirectLocation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ abstract class Controller extends BaseController
|
||||
*/
|
||||
protected function getImageValidationRules(): array
|
||||
{
|
||||
return ['image_extension', 'mimes:jpeg,png,gif,webp', 'max:' . (config('app.upload_limit') * 1000)];
|
||||
return ['image_extension', 'mimes:jpeg,png,gif,webp,avif', 'max:' . (config('app.upload_limit') * 1000)];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,7 +16,7 @@ class ContentPermissionApiController extends ApiController
|
||||
) {
|
||||
}
|
||||
|
||||
protected $rules = [
|
||||
protected array $rules = [
|
||||
'update' => [
|
||||
'owner_id' => ['int'],
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ class JointPermissionBuilder
|
||||
/**
|
||||
* Re-generate all entity permission from scratch.
|
||||
*/
|
||||
public function rebuildForAll()
|
||||
public function rebuildForAll(): void
|
||||
{
|
||||
JointPermission::query()->truncate();
|
||||
|
||||
@@ -51,7 +51,7 @@ class JointPermissionBuilder
|
||||
/**
|
||||
* Rebuild the entity jointPermissions for a particular entity.
|
||||
*/
|
||||
public function rebuildForEntity(Entity $entity)
|
||||
public function rebuildForEntity(Entity $entity): void
|
||||
{
|
||||
$entities = [$entity];
|
||||
if ($entity instanceof Book) {
|
||||
@@ -119,7 +119,7 @@ class JointPermissionBuilder
|
||||
/**
|
||||
* Build joint permissions for the given book and role combinations.
|
||||
*/
|
||||
protected function buildJointPermissionsForBooks(EloquentCollection $books, array $roles, bool $deleteOld = false)
|
||||
protected function buildJointPermissionsForBooks(EloquentCollection $books, array $roles, bool $deleteOld = false): void
|
||||
{
|
||||
$entities = clone $books;
|
||||
|
||||
@@ -143,7 +143,7 @@ class JointPermissionBuilder
|
||||
/**
|
||||
* Rebuild the entity jointPermissions for a collection of entities.
|
||||
*/
|
||||
protected function buildJointPermissionsForEntities(array $entities)
|
||||
protected function buildJointPermissionsForEntities(array $entities): void
|
||||
{
|
||||
$roles = Role::query()->get()->values()->all();
|
||||
$this->deleteManyJointPermissionsForEntities($entities);
|
||||
@@ -155,21 +155,19 @@ class JointPermissionBuilder
|
||||
*
|
||||
* @param Entity[] $entities
|
||||
*/
|
||||
protected function deleteManyJointPermissionsForEntities(array $entities)
|
||||
protected function deleteManyJointPermissionsForEntities(array $entities): void
|
||||
{
|
||||
$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();
|
||||
}
|
||||
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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -195,7 +193,7 @@ class JointPermissionBuilder
|
||||
* @param Entity[] $originalEntities
|
||||
* @param Role[] $roles
|
||||
*/
|
||||
protected function createManyJointPermissions(array $originalEntities, array $roles)
|
||||
protected function createManyJointPermissions(array $originalEntities, array $roles): void
|
||||
{
|
||||
$entities = $this->entitiesToSimpleEntities($originalEntities);
|
||||
$jointPermissions = [];
|
||||
@@ -225,11 +223,9 @@ class JointPermissionBuilder
|
||||
}
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($jointPermissions) {
|
||||
foreach (array_chunk($jointPermissions, 1000) as $jointPermissionChunk) {
|
||||
DB::table('joint_permissions')->insert($jointPermissionChunk);
|
||||
}
|
||||
});
|
||||
foreach (array_chunk($jointPermissions, 1000) as $jointPermissionChunk) {
|
||||
DB::table('joint_permissions')->insert($jointPermissionChunk);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,6 +7,7 @@ use BookStack\Entities\Tools\PermissionsUpdater;
|
||||
use BookStack\Http\Controller;
|
||||
use BookStack\Permissions\Models\EntityPermission;
|
||||
use BookStack\Users\Models\Role;
|
||||
use BookStack\Util\DatabaseTransaction;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class PermissionsController extends Controller
|
||||
@@ -40,7 +41,9 @@ class PermissionsController extends Controller
|
||||
$page = $this->queries->pages->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
|
||||
$this->checkOwnablePermission('restrictions-manage', $page);
|
||||
|
||||
$this->permissionsUpdater->updateFromPermissionsForm($page, $request);
|
||||
(new DatabaseTransaction(function () use ($page, $request) {
|
||||
$this->permissionsUpdater->updateFromPermissionsForm($page, $request);
|
||||
}))->run();
|
||||
|
||||
$this->showSuccessNotification(trans('entities.pages_permissions_success'));
|
||||
|
||||
@@ -70,7 +73,9 @@ class PermissionsController extends Controller
|
||||
$chapter = $this->queries->chapters->findVisibleBySlugsOrFail($bookSlug, $chapterSlug);
|
||||
$this->checkOwnablePermission('restrictions-manage', $chapter);
|
||||
|
||||
$this->permissionsUpdater->updateFromPermissionsForm($chapter, $request);
|
||||
(new DatabaseTransaction(function () use ($chapter, $request) {
|
||||
$this->permissionsUpdater->updateFromPermissionsForm($chapter, $request);
|
||||
}))->run();
|
||||
|
||||
$this->showSuccessNotification(trans('entities.chapters_permissions_success'));
|
||||
|
||||
@@ -100,7 +105,9 @@ class PermissionsController extends Controller
|
||||
$book = $this->queries->books->findVisibleBySlugOrFail($slug);
|
||||
$this->checkOwnablePermission('restrictions-manage', $book);
|
||||
|
||||
$this->permissionsUpdater->updateFromPermissionsForm($book, $request);
|
||||
(new DatabaseTransaction(function () use ($book, $request) {
|
||||
$this->permissionsUpdater->updateFromPermissionsForm($book, $request);
|
||||
}))->run();
|
||||
|
||||
$this->showSuccessNotification(trans('entities.books_permissions_updated'));
|
||||
|
||||
@@ -130,7 +137,9 @@ class PermissionsController extends Controller
|
||||
$shelf = $this->queries->shelves->findVisibleBySlugOrFail($slug);
|
||||
$this->checkOwnablePermission('restrictions-manage', $shelf);
|
||||
|
||||
$this->permissionsUpdater->updateFromPermissionsForm($shelf, $request);
|
||||
(new DatabaseTransaction(function () use ($shelf, $request) {
|
||||
$this->permissionsUpdater->updateFromPermissionsForm($shelf, $request);
|
||||
}))->run();
|
||||
|
||||
$this->showSuccessNotification(trans('entities.shelves_permissions_updated'));
|
||||
|
||||
@@ -145,7 +154,10 @@ class PermissionsController extends Controller
|
||||
$shelf = $this->queries->shelves->findVisibleBySlugOrFail($slug);
|
||||
$this->checkOwnablePermission('restrictions-manage', $shelf);
|
||||
|
||||
$updateCount = $this->permissionsUpdater->updateBookPermissionsFromShelf($shelf);
|
||||
$updateCount = (new DatabaseTransaction(function () use ($shelf) {
|
||||
return $this->permissionsUpdater->updateBookPermissionsFromShelf($shelf);
|
||||
}))->run();
|
||||
|
||||
$this->showSuccessNotification(trans('entities.shelves_copy_permission_success', ['count' => $updateCount]));
|
||||
|
||||
return redirect($shelf->getUrl());
|
||||
|
||||
@@ -7,6 +7,7 @@ use BookStack\Exceptions\PermissionsException;
|
||||
use BookStack\Facades\Activity;
|
||||
use BookStack\Permissions\Models\RolePermission;
|
||||
use BookStack\Users\Models\Role;
|
||||
use BookStack\Util\DatabaseTransaction;
|
||||
use Exception;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
@@ -48,38 +49,42 @@ class PermissionsRepo
|
||||
*/
|
||||
public function saveNewRole(array $roleData): Role
|
||||
{
|
||||
$role = new Role($roleData);
|
||||
$role->mfa_enforced = boolval($roleData['mfa_enforced'] ?? false);
|
||||
$role->save();
|
||||
return (new DatabaseTransaction(function () use ($roleData) {
|
||||
$role = new Role($roleData);
|
||||
$role->mfa_enforced = boolval($roleData['mfa_enforced'] ?? false);
|
||||
$role->save();
|
||||
|
||||
$permissions = $roleData['permissions'] ?? [];
|
||||
$this->assignRolePermissions($role, $permissions);
|
||||
$this->permissionBuilder->rebuildForRole($role);
|
||||
$permissions = $roleData['permissions'] ?? [];
|
||||
$this->assignRolePermissions($role, $permissions);
|
||||
$this->permissionBuilder->rebuildForRole($role);
|
||||
|
||||
Activity::add(ActivityType::ROLE_CREATE, $role);
|
||||
Activity::add(ActivityType::ROLE_CREATE, $role);
|
||||
|
||||
return $role;
|
||||
return $role;
|
||||
}))->run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing role.
|
||||
* Ensures Admin system role always have core permissions.
|
||||
* Ensures the Admin system role always has core permissions.
|
||||
*/
|
||||
public function updateRole($roleId, array $roleData): Role
|
||||
{
|
||||
$role = $this->getRoleById($roleId);
|
||||
|
||||
if (isset($roleData['permissions'])) {
|
||||
$this->assignRolePermissions($role, $roleData['permissions']);
|
||||
}
|
||||
return (new DatabaseTransaction(function () use ($role, $roleData) {
|
||||
if (isset($roleData['permissions'])) {
|
||||
$this->assignRolePermissions($role, $roleData['permissions']);
|
||||
}
|
||||
|
||||
$role->fill($roleData);
|
||||
$role->save();
|
||||
$this->permissionBuilder->rebuildForRole($role);
|
||||
$role->fill($roleData);
|
||||
$role->save();
|
||||
$this->permissionBuilder->rebuildForRole($role);
|
||||
|
||||
Activity::add(ActivityType::ROLE_UPDATE, $role);
|
||||
Activity::add(ActivityType::ROLE_UPDATE, $role);
|
||||
|
||||
return $role;
|
||||
return $role;
|
||||
}))->run();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,7 +119,7 @@ class PermissionsRepo
|
||||
/**
|
||||
* Delete a role from the system.
|
||||
* Check it's not an admin role or set as default before deleting.
|
||||
* If a migration Role ID is specified the users assign to the current role
|
||||
* If a migration Role ID is specified, the users assigned to the current role
|
||||
* will be added to the role of the specified id.
|
||||
*
|
||||
* @throws PermissionsException
|
||||
@@ -131,17 +136,19 @@ class PermissionsRepo
|
||||
throw new PermissionsException(trans('errors.role_registration_default_cannot_delete'));
|
||||
}
|
||||
|
||||
if ($migrateRoleId !== 0) {
|
||||
$newRole = Role::query()->find($migrateRoleId);
|
||||
if ($newRole) {
|
||||
$users = $role->users()->pluck('id')->toArray();
|
||||
$newRole->users()->sync($users);
|
||||
(new DatabaseTransaction(function () use ($migrateRoleId, $role) {
|
||||
if ($migrateRoleId !== 0) {
|
||||
$newRole = Role::query()->find($migrateRoleId);
|
||||
if ($newRole) {
|
||||
$users = $role->users()->pluck('id')->toArray();
|
||||
$newRole->users()->sync($users);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$role->entityPermissions()->delete();
|
||||
$role->jointPermissions()->delete();
|
||||
Activity::add(ActivityType::ROLE_DELETE, $role);
|
||||
$role->delete();
|
||||
$role->entityPermissions()->delete();
|
||||
$role->jointPermissions()->delete();
|
||||
Activity::add(ActivityType::ROLE_DELETE, $role);
|
||||
$role->delete();
|
||||
}))->run();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use Illuminate\Http\Request;
|
||||
|
||||
class SearchApiController extends ApiController
|
||||
{
|
||||
protected $rules = [
|
||||
protected array $rules = [
|
||||
'all' => [
|
||||
'query' => ['required'],
|
||||
'page' => ['integer', 'min:1'],
|
||||
|
||||
@@ -160,7 +160,9 @@ class SearchIndex
|
||||
/** @var DOMNode $child */
|
||||
foreach ($doc->getBodyChildren() as $child) {
|
||||
$nodeName = $child->nodeName;
|
||||
$termCounts = $this->textToTermCountMap(trim($child->textContent));
|
||||
$text = trim($child->textContent);
|
||||
$text = str_replace("\u{00A0}", ' ', $text);
|
||||
$termCounts = $this->textToTermCountMap($text);
|
||||
foreach ($termCounts as $term => $count) {
|
||||
$scoreChange = $count * ($elementScoreAdjustmentMap[$nodeName] ?? 1);
|
||||
$scoresByTerm[$term] = ($scoresByTerm[$term] ?? 0) + $scoreChange;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace BookStack\Settings;
|
||||
|
||||
use BookStack\Activity\ActivityType;
|
||||
use BookStack\App\AppVersion;
|
||||
use BookStack\Entities\Tools\TrashCan;
|
||||
use BookStack\Http\Controller;
|
||||
use BookStack\References\ReferenceStore;
|
||||
@@ -19,14 +20,11 @@ class MaintenanceController extends Controller
|
||||
$this->checkPermission('settings-manage');
|
||||
$this->setPageTitle(trans('settings.maint'));
|
||||
|
||||
// Get application version
|
||||
$version = trim(file_get_contents(base_path('version')));
|
||||
|
||||
// Recycle bin details
|
||||
$recycleStats = $trashCan->getTrashedCounts();
|
||||
|
||||
return view('settings.maintenance', [
|
||||
'version' => $version,
|
||||
'version' => AppVersion::get(),
|
||||
'recycleStats' => $recycleStats,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace BookStack\Settings;
|
||||
|
||||
use BookStack\Activity\ActivityType;
|
||||
use BookStack\App\AppVersion;
|
||||
use BookStack\Http\Controller;
|
||||
use BookStack\Users\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -26,12 +27,9 @@ class SettingController extends Controller
|
||||
$this->checkPermission('settings-manage');
|
||||
$this->setPageTitle(trans('settings.settings'));
|
||||
|
||||
// Get application version
|
||||
$version = trim(file_get_contents(base_path('version')));
|
||||
|
||||
return view('settings.categories.' . $category, [
|
||||
'category' => $category,
|
||||
'version' => $version,
|
||||
'version' => AppVersion::get(),
|
||||
'guestUser' => User::getGuest(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use BookStack\Entities\Queries\BookQueries;
|
||||
use BookStack\Entities\Tools\BookContents;
|
||||
use BookStack\Facades\Activity;
|
||||
use BookStack\Http\Controller;
|
||||
use BookStack\Util\DatabaseTransaction;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class BookSortController extends Controller
|
||||
@@ -55,16 +56,18 @@ class BookSortController extends Controller
|
||||
|
||||
// Sort via map
|
||||
if ($request->filled('sort-tree')) {
|
||||
$sortMap = BookSortMap::fromJson($request->get('sort-tree'));
|
||||
$booksInvolved = $sorter->sortUsingMap($sortMap);
|
||||
(new DatabaseTransaction(function () use ($book, $request, $sorter, &$loggedActivityForBook) {
|
||||
$sortMap = BookSortMap::fromJson($request->get('sort-tree'));
|
||||
$booksInvolved = $sorter->sortUsingMap($sortMap);
|
||||
|
||||
// Rebuild permissions and add activity for involved books.
|
||||
foreach ($booksInvolved as $bookInvolved) {
|
||||
Activity::add(ActivityType::BOOK_SORT, $bookInvolved);
|
||||
if ($bookInvolved->id === $book->id) {
|
||||
$loggedActivityForBook = true;
|
||||
// Add activity for involved books.
|
||||
foreach ($booksInvolved as $bookInvolved) {
|
||||
Activity::add(ActivityType::BOOK_SORT, $bookInvolved);
|
||||
if ($bookInvolved->id === $book->id) {
|
||||
$loggedActivityForBook = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}))->run();
|
||||
}
|
||||
|
||||
if ($request->filled('auto-sort')) {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace BookStack\Sorting;
|
||||
|
||||
use BookStack\App\Model;
|
||||
use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Models\BookChild;
|
||||
use BookStack\Entities\Models\Chapter;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace BookStack\Sorting;
|
||||
|
||||
use voku\helper\ASCII;
|
||||
use BookStack\Entities\Models\Chapter;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
|
||||
@@ -13,12 +14,12 @@ class SortSetOperationComparisons
|
||||
{
|
||||
public static function nameAsc(Entity $a, Entity $b): int
|
||||
{
|
||||
return strtolower($a->name) <=> strtolower($b->name);
|
||||
return strtolower(ASCII::to_transliterate($a->name, null)) <=> strtolower(ASCII::to_transliterate($b->name, null));
|
||||
}
|
||||
|
||||
public static function nameDesc(Entity $a, Entity $b): int
|
||||
{
|
||||
return strtolower($b->name) <=> strtolower($a->name);
|
||||
return strtolower(ASCII::to_transliterate($b->name, null)) <=> strtolower(ASCII::to_transliterate($a->name, null));
|
||||
}
|
||||
|
||||
public static function nameNumericAsc(Entity $a, Entity $b): int
|
||||
|
||||
@@ -50,6 +50,7 @@ class LocaleManager
|
||||
'ku' => 'ku_TR',
|
||||
'lt' => 'lt_LT',
|
||||
'lv' => 'lv_LV',
|
||||
'ne' => 'ne_NP',
|
||||
'nb' => 'nb_NO',
|
||||
'nl' => 'nl_NL',
|
||||
'nn' => 'nn_NO',
|
||||
|
||||
@@ -6,6 +6,7 @@ use BookStack\Exceptions\ImageUploadException;
|
||||
use Exception;
|
||||
use GuzzleHttp\Psr7\Utils;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Intervention\Image\Decoders\BinaryImageDecoder;
|
||||
use Intervention\Image\Drivers\Gd\Decoders\NativeObjectDecoder;
|
||||
use Intervention\Image\Drivers\Gd\Driver;
|
||||
@@ -93,8 +94,8 @@ class ImageResizer
|
||||
|
||||
$imageData = $disk->get($imagePath);
|
||||
|
||||
// Do not resize apng images where we're not cropping
|
||||
if ($keepRatio && $this->isApngData($image, $imageData)) {
|
||||
// Do not resize animated images where we're not cropping
|
||||
if ($keepRatio && $this->isAnimated($image, $imageData)) {
|
||||
Cache::put($thumbCacheKey, $image->path, static::THUMBNAIL_CACHE_TIME);
|
||||
|
||||
return $this->storage->getPublicUrl($image->path);
|
||||
@@ -240,15 +241,50 @@ class ImageResizer
|
||||
/**
|
||||
* Check if the given image and image data is apng.
|
||||
*/
|
||||
protected function isApngData(Image $image, string &$imageData): bool
|
||||
protected function isApngData(string &$imageData): bool
|
||||
{
|
||||
$isPng = strtolower(pathinfo($image->path, PATHINFO_EXTENSION)) === 'png';
|
||||
if (!$isPng) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$initialHeader = substr($imageData, 0, strpos($imageData, 'IDAT'));
|
||||
|
||||
return str_contains($initialHeader, 'acTL');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given avif image data represents an animated image.
|
||||
* This is based up the answer here: https://stackoverflow.com/a/79457313
|
||||
*/
|
||||
protected function isAnimatedAvifData(string &$imageData): bool
|
||||
{
|
||||
$stszPos = strpos($imageData, 'stsz');
|
||||
if ($stszPos === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Look 12 bytes after the start of 'stsz'
|
||||
$start = $stszPos + 12;
|
||||
$end = $start + 4;
|
||||
if ($end > strlen($imageData) - 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = substr($imageData, $start, 4);
|
||||
$count = unpack('Nvalue', $data)['value'];
|
||||
return $count > 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given image is animated.
|
||||
*/
|
||||
protected function isAnimated(Image $image, string &$imageData): bool
|
||||
{
|
||||
$extension = strtolower(pathinfo($image->path, PATHINFO_EXTENSION));
|
||||
if ($extension === 'png') {
|
||||
return $this->isApngData($imageData);
|
||||
}
|
||||
|
||||
if ($extension === 'avif') {
|
||||
return $this->isAnimatedAvifData($imageData);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
class ImageService
|
||||
{
|
||||
protected static array $supportedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
|
||||
protected static array $supportedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'avif'];
|
||||
|
||||
public function __construct(
|
||||
protected ImageStorage $storage,
|
||||
|
||||
@@ -5,6 +5,9 @@ namespace BookStack\Uploads;
|
||||
use BookStack\Util\FilePathNormalizer;
|
||||
use Illuminate\Contracts\Filesystem\Filesystem;
|
||||
use Illuminate\Filesystem\FilesystemAdapter;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use League\Flysystem\UnableToSetVisibility;
|
||||
use League\Flysystem\Visibility;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
class ImageStorageDisk
|
||||
@@ -74,12 +77,19 @@ class ImageStorageDisk
|
||||
$path = $this->adjustPathForDisk($path);
|
||||
$this->filesystem->put($path, $data);
|
||||
|
||||
// Set visibility when a non-AWS-s3, s3-like storage option is in use.
|
||||
// Done since this call can break s3-like services but desired for other image stores.
|
||||
// Attempting to set ACL during above put request requires different permissions
|
||||
// hence would technically be a breaking change for actual s3 usage.
|
||||
// Set public visibility to ensure public access on S3, or that the file is accessible
|
||||
// to other processes (like web-servers) for local file storage options.
|
||||
// We avoid attempting this for (non-AWS) s3-like systems (even in a try-catch) as
|
||||
// we've always avoided setting permissions for s3-like due to potential issues,
|
||||
// with docs advising setting pre-configured permissions instead.
|
||||
// We also don't do this as the default filesystem/driver level as that can technically
|
||||
// require different ACLs for S3, and this provides us more logical control.
|
||||
if ($makePublic && !$this->isS3Like()) {
|
||||
$this->filesystem->setVisibility($path, 'public');
|
||||
try {
|
||||
$this->filesystem->setVisibility($path, Visibility::PUBLIC);
|
||||
} catch (UnableToSetVisibility $e) {
|
||||
Log::warning("Unable to set visibility for image upload with relative path: {$path}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace BookStack\Uploads;
|
||||
use BookStack\Exceptions\HttpFetchException;
|
||||
use BookStack\Http\HttpRequestService;
|
||||
use BookStack\Users\Models\User;
|
||||
use BookStack\Util\WebSafeMimeSniffer;
|
||||
use Exception;
|
||||
use GuzzleHttp\Psr7\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
@@ -53,6 +54,33 @@ class UserAvatars
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a new avatar image to the given user by fetching from a remote URL.
|
||||
*/
|
||||
public function assignToUserFromUrl(User $user, string $avatarUrl): void
|
||||
{
|
||||
try {
|
||||
$this->destroyAllForUser($user);
|
||||
$imageData = $this->getAvatarImageData($avatarUrl);
|
||||
|
||||
$mime = (new WebSafeMimeSniffer())->sniff($imageData);
|
||||
[$format, $type] = explode('/', $mime, 2);
|
||||
if ($format !== 'image' || !ImageService::isExtensionSupported($type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$avatar = $this->createAvatarImageFromData($user, $imageData, $type);
|
||||
$user->avatar()->associate($avatar);
|
||||
$user->save();
|
||||
} catch (Exception $e) {
|
||||
Log::error('Failed to save user avatar image from URL', [
|
||||
'exception' => $e->getMessage(),
|
||||
'url' => $avatarUrl,
|
||||
'user_id' => $user->id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy all user avatars uploaded to the given user.
|
||||
*/
|
||||
@@ -105,7 +133,7 @@ class UserAvatars
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an image from url and returns it as a string of image data.
|
||||
* Get an image from a URL and return it as a string of image data.
|
||||
*
|
||||
* @throws HttpFetchException
|
||||
*/
|
||||
@@ -113,7 +141,19 @@ class UserAvatars
|
||||
{
|
||||
try {
|
||||
$client = $this->http->buildClient(5);
|
||||
$response = $client->sendRequest(new Request('GET', $url));
|
||||
$responseCount = 0;
|
||||
|
||||
do {
|
||||
$response = $client->sendRequest(new Request('GET', $url));
|
||||
$responseCount++;
|
||||
$isRedirect = ($response->getStatusCode() === 301 || $response->getStatusCode() === 302);
|
||||
$url = $response->getHeader('Location')[0] ?? '';
|
||||
} while ($responseCount < 3 && $isRedirect && is_string($url) && str_starts_with($url, 'http'));
|
||||
|
||||
if ($responseCount === 3) {
|
||||
throw new HttpFetchException("Failed to fetch image, max redirect limit of 3 tries reached. Last fetched URL: {$url}");
|
||||
}
|
||||
|
||||
if ($response->getStatusCode() !== 200) {
|
||||
throw new HttpFetchException(trans('errors.cannot_get_image_from_url', ['url' => $url]));
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ class RoleApiController extends ApiController
|
||||
'display_name', 'description', 'mfa_enforced', 'external_auth_id', 'created_at', 'updated_at',
|
||||
];
|
||||
|
||||
protected $rules = [
|
||||
protected array $rules = [
|
||||
'create' => [
|
||||
'display_name' => ['required', 'string', 'min:3', 'max:180'],
|
||||
'description' => ['string', 'max:180'],
|
||||
|
||||
@@ -45,6 +45,7 @@ use Illuminate\Support\Collection;
|
||||
* @property string $system_name
|
||||
* @property Collection $roles
|
||||
* @property Collection $mfaValues
|
||||
* @property ?Image $avatar
|
||||
*/
|
||||
class User extends Model implements AuthenticatableContract, CanResetPasswordContract, Loggable, Sluggable
|
||||
{
|
||||
|
||||
42
app/Util/DatabaseTransaction.php
Normal file
42
app/Util/DatabaseTransaction.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Util;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Run the given code within a database transactions.
|
||||
* Wraps Laravel's own transaction method, but sets a specific runtime isolation method.
|
||||
* This sets a session level since this won't cause issues if already within a transaction,
|
||||
* and this should apply to the next transactions anyway.
|
||||
*
|
||||
* "READ COMMITTED" ensures that changes from other transactions can be read within
|
||||
* a transaction, even if started afterward (and for example, it was blocked by the initial
|
||||
* transaction). This is quite important for things like permission generation, where we would
|
||||
* want to consider the changes made by other committed transactions by the time we come to
|
||||
* regenerate permission access.
|
||||
*
|
||||
* @throws Throwable
|
||||
* @template TReturn of mixed
|
||||
*/
|
||||
class DatabaseTransaction
|
||||
{
|
||||
/**
|
||||
* @param (Closure(static): TReturn) $callback
|
||||
*/
|
||||
public function __construct(
|
||||
protected Closure $callback
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TReturn
|
||||
*/
|
||||
public function run(): mixed
|
||||
{
|
||||
DB::statement('SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED');
|
||||
return DB::transaction($this->callback);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ namespace BookStack\Util;
|
||||
|
||||
use DOMAttr;
|
||||
use DOMElement;
|
||||
use DOMNamedNodeMap;
|
||||
use DOMNode;
|
||||
|
||||
/**
|
||||
@@ -25,6 +24,7 @@ class HtmlDescriptionFilter
|
||||
'ul' => [],
|
||||
'li' => [],
|
||||
'strong' => [],
|
||||
'span' => [],
|
||||
'em' => [],
|
||||
'br' => [],
|
||||
];
|
||||
@@ -59,7 +59,6 @@ class HtmlDescriptionFilter
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var DOMNamedNodeMap $attrs */
|
||||
$attrs = $element->attributes;
|
||||
for ($i = $attrs->length - 1; $i >= 0; $i--) {
|
||||
/** @var DOMAttr $attr */
|
||||
@@ -70,7 +69,8 @@ class HtmlDescriptionFilter
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($element->childNodes as $child) {
|
||||
$childNodes = [...$element->childNodes];
|
||||
foreach ($childNodes as $child) {
|
||||
if ($child instanceof DOMElement) {
|
||||
static::filterElement($child);
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -38,8 +38,7 @@
|
||||
"socialiteproviders/microsoft-azure": "^5.1",
|
||||
"socialiteproviders/okta": "^4.2",
|
||||
"socialiteproviders/twitch": "^5.3",
|
||||
"ssddanbrown/htmldiff": "^1.0.2",
|
||||
"ssddanbrown/symfony-mailer": "7.2.x-dev"
|
||||
"ssddanbrown/htmldiff": "^2.0.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"fakerphp/faker": "^1.21",
|
||||
|
||||
1415
composer.lock
generated
1415
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -27,6 +27,8 @@ class CommentFactory extends Factory
|
||||
'html' => $html,
|
||||
'parent_id' => null,
|
||||
'local_id' => 1,
|
||||
'content_ref' => '',
|
||||
'archived' => false,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ class ImportFactory extends Factory
|
||||
'path' => 'uploads/files/imports/' . Str::random(10) . '.zip',
|
||||
'name' => $this->faker->words(3, true),
|
||||
'type' => 'book',
|
||||
'size' => rand(1, 1001),
|
||||
'metadata' => '{"name": "My book"}',
|
||||
'created_at' => User::factory(),
|
||||
];
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('comments', function (Blueprint $table) {
|
||||
$table->string('content_ref');
|
||||
$table->boolean('archived')->index();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('comments', function (Blueprint $table) {
|
||||
$table->dropColumn('content_ref');
|
||||
$table->dropColumn('archived');
|
||||
});
|
||||
}
|
||||
};
|
||||
4
dev/api/requests/imports-run.json
Normal file
4
dev/api/requests/imports-run.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"parent_type": "book",
|
||||
"parent_id": 28
|
||||
}
|
||||
10
dev/api/responses/imports-create.json
Normal file
10
dev/api/responses/imports-create.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"type": "chapter",
|
||||
"name": "Pension Providers",
|
||||
"created_by": 1,
|
||||
"size": 2757,
|
||||
"path": "uploads\/files\/imports\/ghnxmS3u9QxLWu82.zip",
|
||||
"updated_at": "2025-07-18T14:50:27.000000Z",
|
||||
"created_at": "2025-07-18T14:50:27.000000Z",
|
||||
"id": 31
|
||||
}
|
||||
23
dev/api/responses/imports-list.json
Normal file
23
dev/api/responses/imports-list.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"id": 25,
|
||||
"name": "IT Department",
|
||||
"size": 618462,
|
||||
"type": "book",
|
||||
"created_by": 1,
|
||||
"created_at": "2024-12-20T18:40:38.000000Z",
|
||||
"updated_at": "2024-12-20T18:40:38.000000Z"
|
||||
},
|
||||
{
|
||||
"id": 27,
|
||||
"name": "Clients",
|
||||
"size": 15364,
|
||||
"type": "chapter",
|
||||
"created_by": 1,
|
||||
"created_at": "2025-03-20T12:41:44.000000Z",
|
||||
"updated_at": "2025-03-20T12:41:44.000000Z"
|
||||
}
|
||||
],
|
||||
"total": 2
|
||||
}
|
||||
51
dev/api/responses/imports-read.json
Normal file
51
dev/api/responses/imports-read.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"id": 25,
|
||||
"name": "IT Department",
|
||||
"path": "uploads\/files\/imports\/7YOpZ6sGIEbYdRFL.zip",
|
||||
"size": 618462,
|
||||
"type": "book",
|
||||
"created_by": 1,
|
||||
"created_at": "2024-12-20T18:40:38.000000Z",
|
||||
"updated_at": "2024-12-20T18:40:38.000000Z",
|
||||
"details": {
|
||||
"id": 4,
|
||||
"name": "IT Department",
|
||||
"chapters": [
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Server Systems",
|
||||
"priority": 1,
|
||||
"pages": [
|
||||
{
|
||||
"id": 22,
|
||||
"name": "prod-aws-stonehawk",
|
||||
"priority": 0,
|
||||
"attachments": [],
|
||||
"images": [],
|
||||
"tags": []
|
||||
}
|
||||
],
|
||||
"tags": []
|
||||
}
|
||||
],
|
||||
"pages": [
|
||||
{
|
||||
"id": 23,
|
||||
"name": "Member Onboarding Guide",
|
||||
"priority": 0,
|
||||
"attachments": [],
|
||||
"images": [],
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
"id": 25,
|
||||
"name": "IT Holiday Party Event",
|
||||
"priority": 2,
|
||||
"attachments": [],
|
||||
"images": [],
|
||||
"tags": []
|
||||
}
|
||||
],
|
||||
"tags": []
|
||||
}
|
||||
}
|
||||
14
dev/api/responses/imports-run.json
Normal file
14
dev/api/responses/imports-run.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"id": 1067,
|
||||
"book_id": 28,
|
||||
"slug": "pension-providers",
|
||||
"name": "Pension Providers",
|
||||
"description": "Details on the various pension providers that are available",
|
||||
"priority": 7,
|
||||
"created_at": "2025-07-18T14:53:35.000000Z",
|
||||
"updated_at": "2025-07-18T14:53:36.000000Z",
|
||||
"created_by": 1,
|
||||
"updated_by": 1,
|
||||
"owned_by": 1,
|
||||
"default_template_id": null
|
||||
}
|
||||
7
dev/api/responses/system-read.json
Normal file
7
dev/api/responses/system-read.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"version": "v25.02.4",
|
||||
"instance_id": "1234abcd-cc12-7808-af0a-264cb0cbd611",
|
||||
"app_name": "My BookStack Instance",
|
||||
"app_logo": "https://docs.example.com/uploads/images/system/2025-05/cat-icon.png",
|
||||
"base_url": "https://docs.example.com"
|
||||
}
|
||||
@@ -13,7 +13,7 @@ const entryPoints = {
|
||||
app: path.join(__dirname, '../../resources/js/app.ts'),
|
||||
code: path.join(__dirname, '../../resources/js/code/index.mjs'),
|
||||
'legacy-modes': path.join(__dirname, '../../resources/js/code/legacy-modes.mjs'),
|
||||
markdown: path.join(__dirname, '../../resources/js/markdown/index.mjs'),
|
||||
markdown: path.join(__dirname, '../../resources/js/markdown/index.mts'),
|
||||
wysiwyg: path.join(__dirname, '../../resources/js/wysiwyg/index.ts'),
|
||||
};
|
||||
|
||||
|
||||
1
dev/checksums/.gitignore
vendored
Normal file
1
dev/checksums/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!.gitignore
|
||||
1
dev/checksums/vendor
Normal file
1
dev/checksums/vendor
Normal file
@@ -0,0 +1 @@
|
||||
ecb1c038267dde9336e703e14a89d43848880ebb630465e313b4726570f2db04
|
||||
@@ -345,7 +345,7 @@ Link: tj/co
|
||||
codemirror
|
||||
License: MIT
|
||||
License File: node_modules/codemirror/LICENSE
|
||||
Copyright: Copyright (C) 2018-2021 by Marijn Haverbeke <*******@*****.***> and others
|
||||
Copyright: Copyright (C) 2018-2021 by Marijn Haverbeke <******@*********.******> and others
|
||||
Source: https://github.com/codemirror/basic-setup.git
|
||||
Link: https://github.com/codemirror/basic-setup.git
|
||||
-----------
|
||||
@@ -711,13 +711,13 @@ eslint-scope
|
||||
License: BSD-2-Clause
|
||||
License File: node_modules/eslint-scope/LICENSE
|
||||
Copyright: Copyright (C) 2012-2013 Yusuke Suzuki (twitter: @Constellation) and other contributors.
|
||||
Source: eslint/js
|
||||
Source: https://github.com/eslint/js.git
|
||||
Link: https://github.com/eslint/js/blob/main/packages/eslint-scope/README.md
|
||||
-----------
|
||||
eslint-visitor-keys
|
||||
License: Apache-2.0
|
||||
License File: node_modules/eslint-visitor-keys/LICENSE
|
||||
Source: eslint/js
|
||||
Source: https://github.com/eslint/js.git
|
||||
Link: https://github.com/eslint/js/blob/main/packages/eslint-visitor-keys/README.md
|
||||
-----------
|
||||
eslint
|
||||
@@ -731,7 +731,7 @@ License: BSD-2-Clause
|
||||
License File: node_modules/espree/LICENSE
|
||||
Copyright: Copyright (c) Open JS Foundation
|
||||
All rights reserved.
|
||||
Source: eslint/js
|
||||
Source: https://github.com/eslint/js.git
|
||||
Link: https://github.com/eslint/js/blob/main/packages/espree/README.md
|
||||
-----------
|
||||
esprima
|
||||
@@ -1252,6 +1252,13 @@ Copyright: Copyright (c) 2019 Inspect JS
|
||||
Source: git+https://github.com/inspect-js/is-map.git
|
||||
Link: https://github.com/inspect-js/is-map#readme
|
||||
-----------
|
||||
is-negative-zero
|
||||
License: MIT
|
||||
License File: node_modules/is-negative-zero/LICENSE
|
||||
Copyright: Copyright (c) 2014 Jordan Harband
|
||||
Source: git://github.com/inspect-js/is-negative-zero.git
|
||||
Link: https://github.com/inspect-js/is-negative-zero
|
||||
-----------
|
||||
is-number-object
|
||||
License: MIT
|
||||
License File: node_modules/is-number-object/LICENSE
|
||||
@@ -1751,13 +1758,6 @@ Copyright: Copyright (c) Isaac Z. Schlueter and Contributors
|
||||
Source: git://github.com/isaacs/node-lru-cache.git
|
||||
Link: git://github.com/isaacs/node-lru-cache.git
|
||||
-----------
|
||||
magic-string
|
||||
License: MIT
|
||||
License File: node_modules/magic-string/LICENSE
|
||||
Copyright: Copyright 2018 Rich Harris
|
||||
Source: https://github.com/rich-harris/magic-string
|
||||
Link: https://github.com/rich-harris/magic-string
|
||||
-----------
|
||||
make-dir
|
||||
License: MIT
|
||||
License File: node_modules/make-dir/license
|
||||
@@ -1935,9 +1935,9 @@ Link: sindresorhus/npm-run-path
|
||||
nwsapi
|
||||
License: MIT
|
||||
License File: node_modules/nwsapi/LICENSE
|
||||
Copyright: Copyright (c) 2007-2024 Diego Perini (http://www.iport.it/)
|
||||
Copyright: Copyright (c) 2007-2025 Diego Perini (http://www.iport.it/)
|
||||
Source: git://github.com/dperini/nwsapi.git
|
||||
Link: http://javascript.nwbox.com/nwsapi/
|
||||
Link: https://javascript.nwbox.com/nwsapi/
|
||||
-----------
|
||||
object-inspect
|
||||
License: MIT
|
||||
@@ -2281,18 +2281,6 @@ Copyright: Copyright (c) 2012 James Halliday
|
||||
Source: git://github.com/browserify/resolve.git
|
||||
Link: git://github.com/browserify/resolve.git
|
||||
-----------
|
||||
rollup-plugin-dts
|
||||
License: LGPL-3.0
|
||||
Source: git+https://github.com/Swatinem/rollup-plugin-dts.git
|
||||
Link: https://github.com/Swatinem/rollup-plugin-dts#readme
|
||||
-----------
|
||||
rollup
|
||||
License: MIT
|
||||
License File: node_modules/rollup/LICENSE.md
|
||||
Copyright: Copyright (c) 2017 [these people](https://github.com/rollup/rollup/graphs/contributors)
|
||||
Source: rollup/rollup
|
||||
Link: https://rollupjs.org/
|
||||
-----------
|
||||
safe-array-concat
|
||||
License: MIT
|
||||
License File: node_modules/safe-array-concat/LICENSE
|
||||
@@ -2512,6 +2500,13 @@ Copyright: Copyright (c) 2016-2022 Isaac Z. Schlueter <*@***.**>, James Talmage
|
||||
Source: tapjs/stack-utils
|
||||
Link: tapjs/stack-utils
|
||||
-----------
|
||||
stop-iteration-iterator
|
||||
License: MIT
|
||||
License File: node_modules/stop-iteration-iterator/LICENSE
|
||||
Copyright: Copyright (c) 2023 Jordan Harband
|
||||
Source: git+https://github.com/ljharb/stop-iteration-iterator.git
|
||||
Link: https://github.com/ljharb/stop-iteration-iterator#readme
|
||||
-----------
|
||||
string-length
|
||||
License: MIT
|
||||
License File: node_modules/string-length/license
|
||||
@@ -3011,6 +3006,13 @@ Copyright: Copyright (c) 2014-present Sebastian McKenzie and other contributors
|
||||
Source: https://github.com/babel/babel.git
|
||||
Link: https://github.com/babel/babel.git
|
||||
-----------
|
||||
@babel/helper-globals
|
||||
License: MIT
|
||||
License File: node_modules/@babel/helper-globals/LICENSE
|
||||
Copyright: Copyright (c) 2014-present Sebastian McKenzie and other contributors
|
||||
Source: https://github.com/babel/babel.git
|
||||
Link: https://github.com/babel/babel.git
|
||||
-----------
|
||||
@babel/helper-module-imports
|
||||
License: MIT
|
||||
License File: node_modules/@babel/helper-module-imports/LICENSE
|
||||
@@ -3057,6 +3059,7 @@ Link: https://github.com/babel/babel.git
|
||||
License: MIT
|
||||
License File: node_modules/@babel/helpers/LICENSE
|
||||
Copyright: Copyright (c) 2014-present Sebastian McKenzie and other contributors
|
||||
Copyright (c) 2014-present, Facebook, Inc. (ONLY ./src/helpers/regenerator* files)
|
||||
Source: https://github.com/babel/babel.git
|
||||
Link: https://babel.dev/docs/en/next/babel-helpers
|
||||
-----------
|
||||
@@ -3251,7 +3254,7 @@ Link: https://github.com/codemirror/lang-javascript.git
|
||||
@codemirror/lang-json
|
||||
License: MIT
|
||||
License File: node_modules/@codemirror/lang-json/LICENSE
|
||||
Copyright: Copyright (C) 2018-2021 by Marijn Haverbeke <*******@*****.***> and others
|
||||
Copyright: Copyright (C) 2018-2021 by Marijn Haverbeke <******@*********.******> and others
|
||||
Source: https://github.com/codemirror/lang-json.git
|
||||
Link: https://github.com/codemirror/lang-json.git
|
||||
-----------
|
||||
@@ -3265,7 +3268,7 @@ Link: https://github.com/codemirror/lang-markdown.git
|
||||
@codemirror/lang-php
|
||||
License: MIT
|
||||
License File: node_modules/@codemirror/lang-php/LICENSE
|
||||
Copyright: Copyright (C) 2018-2021 by Marijn Haverbeke <*******@*****.***> and others
|
||||
Copyright: Copyright (C) 2018-2021 by Marijn Haverbeke <******@*********.******> and others
|
||||
Source: https://github.com/codemirror/lang-php.git
|
||||
Link: https://github.com/codemirror/lang-php.git
|
||||
-----------
|
||||
@@ -3355,13 +3358,19 @@ Link: https://github.com/eslint-community/regexpp#readme
|
||||
License: Apache-2.0
|
||||
License File: node_modules/@eslint/config-array/LICENSE
|
||||
Source: git+https://github.com/eslint/rewrite.git
|
||||
Link: https://github.com/eslint/rewrite#readme
|
||||
Link: https://github.com/eslint/rewrite/tree/main/packages/config-array#readme
|
||||
-----------
|
||||
@eslint/config-helpers
|
||||
License: Apache-2.0
|
||||
License File: node_modules/@eslint/config-helpers/LICENSE
|
||||
Source: git+https://github.com/eslint/rewrite.git
|
||||
Link: https://github.com/eslint/rewrite/tree/main/packages/config-helpers#readme
|
||||
-----------
|
||||
@eslint/core
|
||||
License: Apache-2.0
|
||||
License File: node_modules/@eslint/core/LICENSE
|
||||
Source: git+https://github.com/eslint/rewrite.git
|
||||
Link: https://github.com/eslint/rewrite#readme
|
||||
Link: https://github.com/eslint/rewrite/tree/main/packages/core#readme
|
||||
-----------
|
||||
@eslint/eslintrc
|
||||
License: MIT
|
||||
@@ -3385,7 +3394,7 @@ Link: https://github.com/eslint/rewrite#readme
|
||||
License: Apache-2.0
|
||||
License File: node_modules/@eslint/plugin-kit/LICENSE
|
||||
Source: git+https://github.com/eslint/rewrite.git
|
||||
Link: https://github.com/eslint/rewrite#readme
|
||||
Link: https://github.com/eslint/rewrite/tree/main/packages/plugin-kit#readme
|
||||
-----------
|
||||
@humanfs/core
|
||||
License: Apache-2.0
|
||||
@@ -3526,9 +3535,9 @@ Link: https://github.com/jestjs/jest.git
|
||||
@jridgewell/gen-mapping
|
||||
License: MIT
|
||||
License File: node_modules/@jridgewell/gen-mapping/LICENSE
|
||||
Copyright: Copyright 2022 Justin Ridgewell <**********@******.***>
|
||||
Source: https://github.com/jridgewell/gen-mapping
|
||||
Link: https://github.com/jridgewell/gen-mapping
|
||||
Copyright: Copyright 2024 Justin Ridgewell <******@*********.****>
|
||||
Source: git+https://github.com/jridgewell/sourcemaps.git
|
||||
Link: https://github.com/jridgewell/sourcemaps/tree/main/packages/gen-mapping
|
||||
-----------
|
||||
@jridgewell/resolve-uri
|
||||
License: MIT
|
||||
@@ -3537,26 +3546,19 @@ Copyright: Copyright 2019 Justin Ridgewell <**********@******.***>
|
||||
Source: https://github.com/jridgewell/resolve-uri
|
||||
Link: https://github.com/jridgewell/resolve-uri
|
||||
-----------
|
||||
@jridgewell/set-array
|
||||
License: MIT
|
||||
License File: node_modules/@jridgewell/set-array/LICENSE
|
||||
Copyright: Copyright 2022 Justin Ridgewell <**********@******.***>
|
||||
Source: https://github.com/jridgewell/set-array
|
||||
Link: https://github.com/jridgewell/set-array
|
||||
-----------
|
||||
@jridgewell/sourcemap-codec
|
||||
License: MIT
|
||||
License File: node_modules/@jridgewell/sourcemap-codec/LICENSE
|
||||
Copyright: Copyright (c) 2015 Rich Harris
|
||||
Source: git+https://github.com/jridgewell/sourcemap-codec.git
|
||||
Link: git+https://github.com/jridgewell/sourcemap-codec.git
|
||||
Copyright: Copyright 2024 Justin Ridgewell <******@*********.****>
|
||||
Source: git+https://github.com/jridgewell/sourcemaps.git
|
||||
Link: https://github.com/jridgewell/sourcemaps/tree/main/packages/sourcemap-codec
|
||||
-----------
|
||||
@jridgewell/trace-mapping
|
||||
License: MIT
|
||||
License File: node_modules/@jridgewell/trace-mapping/LICENSE
|
||||
Copyright: Copyright 2022 Justin Ridgewell <******@*********.****>
|
||||
Source: git+https://github.com/jridgewell/trace-mapping.git
|
||||
Link: git+https://github.com/jridgewell/trace-mapping.git
|
||||
Copyright: Copyright 2024 Justin Ridgewell <******@*********.****>
|
||||
Source: git+https://github.com/jridgewell/sourcemaps.git
|
||||
Link: https://github.com/jridgewell/sourcemaps/tree/main/packages/trace-mapping
|
||||
-----------
|
||||
@lezer/common
|
||||
License: MIT
|
||||
@@ -3635,13 +3637,6 @@ Copyright: Copyright (C) 2018 by Marijn Haverbeke <******@*********.******> and
|
||||
Source: https://github.com/lezer-parser/xml.git
|
||||
Link: https://github.com/lezer-parser/xml.git
|
||||
-----------
|
||||
@marijn/buildtool
|
||||
License: MIT
|
||||
License File: node_modules/@marijn/buildtool/LICENSE
|
||||
Copyright: Copyright (C) 2022 by Marijn Haverbeke <******@*********.******> and others
|
||||
Source: https://github.com/marijnh/buildtool.git
|
||||
Link: https://github.com/marijnh/buildtool.git
|
||||
-----------
|
||||
@marijn/find-cluster-break
|
||||
License: MIT
|
||||
License File: node_modules/@marijn/find-cluster-break/LICENSE
|
||||
@@ -3656,6 +3651,13 @@ Copyright: Copyright (c) 2017-present Devon Govett
|
||||
Source: https://github.com/parcel-bundler/watcher.git
|
||||
Link: https://github.com/parcel-bundler/watcher.git
|
||||
-----------
|
||||
@parcel/watcher-linux-x64-musl
|
||||
License: MIT
|
||||
License File: node_modules/@parcel/watcher-linux-x64-musl/LICENSE
|
||||
Copyright: Copyright (c) 2017-present Devon Govett
|
||||
Source: https://github.com/parcel-bundler/watcher.git
|
||||
Link: https://github.com/parcel-bundler/watcher.git
|
||||
-----------
|
||||
@parcel/watcher
|
||||
License: MIT
|
||||
License File: node_modules/@parcel/watcher/LICENSE
|
||||
@@ -3825,12 +3827,26 @@ License: MIT
|
||||
Source: https://www.github.com/DefinitelyTyped/DefinitelyTyped.git
|
||||
Link: https://www.github.com/DefinitelyTyped/DefinitelyTyped.git
|
||||
-----------
|
||||
@types/mocha
|
||||
@types/linkify-it
|
||||
License: MIT
|
||||
License File: node_modules/@types/mocha/LICENSE
|
||||
License File: node_modules/@types/linkify-it/LICENSE
|
||||
Copyright: Copyright (c) Microsoft Corporation.
|
||||
Source: https://github.com/DefinitelyTyped/DefinitelyTyped.git
|
||||
Link: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/mocha
|
||||
Link: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/linkify-it
|
||||
-----------
|
||||
@types/markdown-it
|
||||
License: MIT
|
||||
License File: node_modules/@types/markdown-it/LICENSE
|
||||
Copyright: Copyright (c) Microsoft Corporation.
|
||||
Source: https://github.com/DefinitelyTyped/DefinitelyTyped.git
|
||||
Link: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/markdown-it
|
||||
-----------
|
||||
@types/mdurl
|
||||
License: MIT
|
||||
License File: node_modules/@types/mdurl/LICENSE
|
||||
Copyright: Copyright (c) Microsoft Corporation.
|
||||
Source: https://github.com/DefinitelyTyped/DefinitelyTyped.git
|
||||
Link: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/mdurl
|
||||
-----------
|
||||
@types/node
|
||||
License: MIT
|
||||
|
||||
@@ -486,7 +486,7 @@ Link: https://github.com/ramsey/collection.git
|
||||
ramsey/uuid
|
||||
License: MIT
|
||||
License File: vendor/ramsey/uuid/LICENSE
|
||||
Copyright: Copyright (c) 2012-2023 Ben Ramsey <***@*********.***>
|
||||
Copyright: Copyright (c) 2012-2025 Ben Ramsey <***@*********.***>
|
||||
Source: https://github.com/ramsey/uuid.git
|
||||
Link: https://github.com/ramsey/uuid.git
|
||||
-----------
|
||||
@@ -543,13 +543,6 @@ Copyright: Copyright (c) 2024 Nathan Herald, Rohland de Charmoy, Dan Brown
|
||||
Source: https://codeberg.org/danb/HtmlDiff
|
||||
Link: https://codeberg.org/danb/HtmlDiff
|
||||
-----------
|
||||
ssddanbrown/symfony-mailer
|
||||
License: MIT
|
||||
License File: vendor/ssddanbrown/symfony-mailer/LICENSE
|
||||
Copyright: Copyright (c) 2019-present Fabien Potencier
|
||||
Source: https://github.com/ssddanbrown/symfony-mailer.git
|
||||
Link: https://symfony.com
|
||||
-----------
|
||||
symfony/clock
|
||||
License: MIT
|
||||
License File: vendor/symfony/clock/LICENSE
|
||||
@@ -620,6 +613,13 @@ Copyright: Copyright (c) 2004-present Fabien Potencier
|
||||
Source: https://github.com/symfony/http-kernel.git
|
||||
Link: https://symfony.com
|
||||
-----------
|
||||
symfony/mailer
|
||||
License: MIT
|
||||
License File: vendor/symfony/mailer/LICENSE
|
||||
Copyright: Copyright (c) 2019-present Fabien Potencier
|
||||
Source: https://github.com/symfony/mailer.git
|
||||
Link: https://symfony.com
|
||||
-----------
|
||||
symfony/mime
|
||||
License: MIT
|
||||
License File: vendor/symfony/mime/LICENSE
|
||||
|
||||
@@ -64,12 +64,12 @@ return [
|
||||
// Auth
|
||||
'auth_login' => 'تم تسجيل الدخول',
|
||||
'auth_register' => 'سجل كمستخدم جديد',
|
||||
'auth_password_reset_request' => 'طلب رابط جديد لإعادة تعيين كلمة المرور',
|
||||
'auth_password_reset_request' => 'طلب رابط جديد لإعادة تعيين كلمة السر',
|
||||
'auth_password_reset_update' => 'إعادة تعيين كلمة مرور المستخدم',
|
||||
'mfa_setup_method' => 'طريقة MFA المكونة',
|
||||
'mfa_setup_method_notification' => 'تم تكوين طريقة متعددة العوامل بنجاح',
|
||||
'mfa_remove_method' => 'إزالة طريقة MFA',
|
||||
'mfa_remove_method_notification' => 'تمت إزالة طريقة متعددة العوامل بنجاح',
|
||||
'mfa_setup_method' => 'طريقة المصادقة متعددة العوامل المُهيأة',
|
||||
'mfa_setup_method_notification' => 'تم إعداد المصادقة متعددة العوامل بنجاح',
|
||||
'mfa_remove_method' => 'إزالة طريقة المصادقة متعددة العوامل',
|
||||
'mfa_remove_method_notification' => 'تمت إزالة المصادقة متعددة العوامل بنجاح',
|
||||
|
||||
// Settings
|
||||
'settings_update' => 'تحديث الإعدادات',
|
||||
@@ -77,36 +77,36 @@ return [
|
||||
'maintenance_action_run' => 'إجراء الصيانة',
|
||||
|
||||
// Webhooks
|
||||
'webhook_create' => 'تم إنشاء webhook',
|
||||
'webhook_create_notification' => 'تم إنشاء Webhook بنجاح',
|
||||
'webhook_update' => 'تم تحديث webhook',
|
||||
'webhook_update_notification' => 'تم تحديث Webhook بنجاح',
|
||||
'webhook_delete' => 'حذف webhook',
|
||||
'webhook_delete_notification' => 'تم حذف Webhook بنجاح',
|
||||
'webhook_create' => 'تم إنشاء خطاف ويب',
|
||||
'webhook_create_notification' => 'تم إنشاء خطاف ويب بنجاح',
|
||||
'webhook_update' => 'تم تحديث خطاف الويب',
|
||||
'webhook_update_notification' => 'تم تحديث خطاف الويب بنجاح',
|
||||
'webhook_delete' => 'حذف خطاف ويب',
|
||||
'webhook_delete_notification' => 'تم حذف خطاف الويب بنجاح',
|
||||
|
||||
// Imports
|
||||
'import_create' => 'created import',
|
||||
'import_create_notification' => 'Import successfully uploaded',
|
||||
'import_run' => 'updated import',
|
||||
'import_run_notification' => 'Content successfully imported',
|
||||
'import_delete' => 'deleted import',
|
||||
'import_delete_notification' => 'Import successfully deleted',
|
||||
'import_create' => 'تم إنشاء الاستيراد',
|
||||
'import_create_notification' => 'تم رفع الاستيراد بنجاح',
|
||||
'import_run' => 'تم تحديث الاستيراد',
|
||||
'import_run_notification' => 'تم استيراد المحتوى بنجاح',
|
||||
'import_delete' => 'تم حذف الاستيراد',
|
||||
'import_delete_notification' => 'تم الاستيراد بنجاح',
|
||||
|
||||
// Users
|
||||
'user_create' => 'إنشاء مستخدم',
|
||||
'user_create_notification' => 'تم انشاء الحساب',
|
||||
'user_create_notification' => 'تم إنشاء الحساب',
|
||||
'user_update' => 'المستخدم المحدث',
|
||||
'user_update_notification' => 'تم تحديث المستخدم بنجاح',
|
||||
'user_delete' => 'المستخدم المحذوف',
|
||||
'user_delete_notification' => 'تم إزالة المستخدم بنجاح',
|
||||
|
||||
// API Tokens
|
||||
'api_token_create' => 'created API token',
|
||||
'api_token_create_notification' => 'تم إنشاء رمز الـ API بنجاح',
|
||||
'api_token_update' => 'updated API token',
|
||||
'api_token_update_notification' => 'تم تحديث رمز الـ API بنجاح',
|
||||
'api_token_delete' => 'deleted API token',
|
||||
'api_token_delete_notification' => 'تم حذف رمز الـ API بنجاح',
|
||||
'api_token_create' => 'تم إنشاء رمز واجهة برمجة التطبيقات -API-',
|
||||
'api_token_create_notification' => 'تم إنشاء واجهة برمجة التطبيقات -API- بنجاح',
|
||||
'api_token_update' => 'رمز واجهة برمجة التطبيقات المحدث',
|
||||
'api_token_update_notification' => 'تم تحديث رمز واجهة برمجة التطبيقات -API- بنجاح',
|
||||
'api_token_delete' => 'رمز واجهة برمجة التطبيقات المحذوف',
|
||||
'api_token_delete_notification' => 'تم حذف رمز واجهة برمجة التطبيقات -API- بنجاح',
|
||||
|
||||
// Roles
|
||||
'role_create' => 'إنشاء صَلاحِيَة',
|
||||
@@ -128,13 +128,13 @@ return [
|
||||
'comment_delete' => 'تعليق محذوف',
|
||||
|
||||
// Sort Rules
|
||||
'sort_rule_create' => 'created sort rule',
|
||||
'sort_rule_create_notification' => 'Sort rule successfully created',
|
||||
'sort_rule_update' => 'updated sort rule',
|
||||
'sort_rule_update_notification' => 'Sort rule successfully updated',
|
||||
'sort_rule_delete' => 'deleted sort rule',
|
||||
'sort_rule_delete_notification' => 'Sort rule successfully deleted',
|
||||
'sort_rule_create' => 'تم إنشاء قاعدة الفرز',
|
||||
'sort_rule_create_notification' => 'تم إنشاء قاعدة الفرز بنجاح',
|
||||
'sort_rule_update' => 'تم تحديث قاعدة الفرز',
|
||||
'sort_rule_update_notification' => 'تم تحديث قاعدة الفرز بنجاح',
|
||||
'sort_rule_delete' => 'تم حذف قاعدة الفرز',
|
||||
'sort_rule_delete_notification' => 'تم حذف قاعدة الفرز بنجاح',
|
||||
|
||||
// Other
|
||||
'permissions_update' => 'تحديث الأذونات',
|
||||
'permissions_update' => 'تحديث الصلاحيات',
|
||||
];
|
||||
|
||||
@@ -7,26 +7,26 @@
|
||||
return [
|
||||
|
||||
'failed' => 'البيانات المعطاة لا توافق سجلاتنا.',
|
||||
'throttle' => 'تجاوزت الحد الأقصى من المحاولات. الرجاء المحاولة مرة أخرى بعد :seconds seconds.',
|
||||
'throttle' => 'تجاوزت الحد الأقصى من المحاولات. الرجاء المحاولة مرة أخرى بعد :seconds ثانية/ثواني.',
|
||||
|
||||
// Login & Register
|
||||
'sign_up' => 'إنشاء حساب',
|
||||
'log_in' => 'تسجيل الدخول',
|
||||
'log_in_with' => 'تسجيل الدخول باستخدام :socialDriver',
|
||||
'sign_up_with' => 'إنشاء حساب باستخدام :socialDriver',
|
||||
'logout' => 'تسجيل الخروج',
|
||||
'logout' => 'الخروج',
|
||||
|
||||
'name' => 'الاسم',
|
||||
'username' => 'اسم المستخدم',
|
||||
'email' => 'البريد الإلكتروني',
|
||||
'password' => 'كلمة المرور',
|
||||
'password_confirm' => 'تأكيد كلمة المرور',
|
||||
'password_hint' => 'يجب أن تحتوي كلمة المرور على 8 خانات على الأقل',
|
||||
'forgot_password' => 'نسيت كلمة المرور؟',
|
||||
'password' => 'كلمة السر',
|
||||
'password_confirm' => 'تأكيد كلمة السر',
|
||||
'password_hint' => 'يجب أن تحتوي كلمة السر على 8 خانات على الأقل',
|
||||
'forgot_password' => 'نسيت كلمة السر؟',
|
||||
'remember_me' => 'تذكرني',
|
||||
'ldap_email_hint' => 'الرجاء إدخال عنوان بريد إلكتروني لاستخدامه مع الحساب.',
|
||||
'create_account' => 'إنشاء حساب',
|
||||
'already_have_account' => 'لديك حساب بالفعل؟',
|
||||
'already_have_account' => 'لديك حساب مسبقاً؟',
|
||||
'dont_have_account' => 'ليس لديك حساب؟',
|
||||
'social_login' => 'تسجيل الدخول باستخدام حسابات التواصل الاجتماعي',
|
||||
'social_registration' => 'إنشاء حساب باستخدام حسابات التواصل الاجتماعي',
|
||||
@@ -44,14 +44,14 @@ return [
|
||||
'auto_init_start_link' => 'المتابعة مع المصادقة',
|
||||
|
||||
// Password Reset
|
||||
'reset_password' => 'استعادة كلمة المرور',
|
||||
'reset_password_send_instructions' => 'أدخل بريدك الإلكتروني بالأسفل وسيتم إرسال رسالة برابط لاستعادة كلمة المرور.',
|
||||
'reset_password' => 'استعادة كلمة السر',
|
||||
'reset_password_send_instructions' => 'أدخل بريدك الإلكتروني بالأسفل وسيتم إرسال رسالة برابط لاستعادة كلمة السر.',
|
||||
'reset_password_send_button' => 'أرسل رابط الاستعادة',
|
||||
'reset_password_sent' => 'سيتم إرسال رابط إعادة تعيين كلمة المرور إلى عنوان البريد الإلكتروني هذا إذا كان موجودًا في النظام.',
|
||||
'reset_password_success' => 'تمت استعادة كلمة المرور بنجاح.',
|
||||
'email_reset_subject' => 'استعد كلمة المرور الخاصة بتطبيق :appName',
|
||||
'email_reset_text' => 'تم إرسال هذه الرسالة بسبب تلقينا لطلب استعادة كلمة المرور الخاصة بحسابكم.',
|
||||
'email_reset_not_requested' => 'إذا لم يتم طلب استعادة كلمة المرور من قبلكم، فلا حاجة لاتخاذ أية خطوات.',
|
||||
'reset_password_sent' => 'سيتم إرسال رابط إعادة تعيين كلمة السر إلى عنوان البريد الإلكتروني هذا إذا كان موجودًا في النظام.',
|
||||
'reset_password_success' => 'تمت استعادة كلمة السر بنجاح.',
|
||||
'email_reset_subject' => 'استعد كلمة السر الخاصة بتطبيق :appName',
|
||||
'email_reset_text' => 'تم إرسال هذه الرسالة بسبب تلقينا لطلب استعادة كلمة السر الخاصة بحسابكم.',
|
||||
'email_reset_not_requested' => 'إذا لم يتم طلب استعادة كلمة السر من قبلكم، فلا حاجة لاتخاذ أية خطوات.',
|
||||
|
||||
// Email Confirmation
|
||||
'email_confirm_subject' => 'تأكيد بريدكم الإلكتروني لتطبيق :appName',
|
||||
@@ -60,7 +60,7 @@ return [
|
||||
'email_confirm_action' => 'تأكيد البريد الإلكتروني',
|
||||
'email_confirm_send_error' => 'تأكيد البريد الإلكتروني مطلوب ولكن النظام لم يستطع إرسال الرسالة. تواصل مع مشرف النظام للتأكد من إعدادات البريد.',
|
||||
'email_confirm_success' => 'تم تأكيد بريدك الإلكتروني! يمكنك الآن تسجيل الدخول باستخدام عنوان البريد الإلكتروني هذا.',
|
||||
'email_confirm_resent' => 'تمت إعادة إرسال رسالة التأكيد. الرجاء مراجعة صندوق الوارد',
|
||||
'email_confirm_resent' => 'تمت إعادة إرسال رسالة التأكيد، الرجاء مراجعة صندوق الوارد.',
|
||||
'email_confirm_thanks' => 'شكرا للتأكيد!',
|
||||
'email_confirm_thanks_desc' => 'الرجاء الانتظار لحظة بينما يتم التعامل مع التأكيد الخاص بك. إذا لم يتم إعادة توجيهك بعد 3 ثوان اضغط على الرابط "المتابعة" أدناه للمتابعة.',
|
||||
|
||||
@@ -72,46 +72,46 @@ return [
|
||||
|
||||
// User Invite
|
||||
'user_invite_email_subject' => 'تمت دعوتك للانضمام إلى صفحة الحالة الخاصة بـ :app_name!',
|
||||
'user_invite_email_greeting' => 'تم إنشاء حساب مستخدم لك على %site%.',
|
||||
'user_invite_email_text' => 'انقر على الزر أدناه لتعيين كلمة مرور الحساب والحصول على الوصول:',
|
||||
'user_invite_email_greeting' => 'تم إنشاء حساب مستخدم لك على :appName.',
|
||||
'user_invite_email_text' => 'انقر على الزر أدناه لتعيين كلمة سر الحساب والحصول على الوصول:',
|
||||
'user_invite_email_action' => 'كلمة سر المستخدم',
|
||||
'user_invite_page_welcome' => 'مرحبا بكم في :appName!',
|
||||
'user_invite_page_text' => 'لإكمال حسابك والحصول على حق الوصول تحتاج إلى تعيين كلمة مرور سيتم استخدامها لتسجيل الدخول إلى :appName في الزيارات المستقبلية.',
|
||||
'user_invite_page_confirm_button' => 'تأكيد كلمة المرور',
|
||||
'user_invite_success_login' => 'تم تأكيد كلمة المرور. يمكنك الآن تسجيل الدخول باستخدام كلمة المرور المحددة للوصول إلى :appName!',
|
||||
'user_invite_page_text' => 'لإكمال حسابك والحصول على حق الوصول تحتاج إلى تعيين كلمة السر سيتم استخدامها لتسجيل الدخول إلى :appName في الزيارات المستقبلية.',
|
||||
'user_invite_page_confirm_button' => 'تأكيد كلمة السر',
|
||||
'user_invite_success_login' => 'تم تأكيد كلمة السر. يمكنك الآن تسجيل الدخول باستخدام كلمة السر المحددة للوصول إلى :appName !',
|
||||
|
||||
// Multi-factor Authentication
|
||||
'mfa_setup' => 'إعداد المصادقة متعددة العوامل',
|
||||
'mfa_setup_desc' => 'إعداد المصادقة متعددة العوامل كطبقة إضافية من الأمان لحساب المستخدم الخاص بك.',
|
||||
'mfa_setup_configured' => 'تم إعداده مسبقاً',
|
||||
'mfa_setup_reconfigure' => 'إعادة التكوين',
|
||||
'mfa_setup_remove_confirmation' => 'هل أنت متأكد من أنك تريد إزالة طريقة المصادقة متعددة العناصر هذه؟',
|
||||
'mfa_setup_action' => 'إعداد (تنصيب)',
|
||||
'mfa_backup_codes_usage_limit_warning' => 'You have less than 5 backup codes remaining, Please generate and store a new set before you run out of codes to prevent being locked out of your account.',
|
||||
'mfa_setup_remove_confirmation' => 'متأكد من أنك تريد إزالة طريقة المصادقة متعددة العوامل هذه؟',
|
||||
'mfa_setup_action' => 'إعداد',
|
||||
'mfa_backup_codes_usage_limit_warning' => 'لديك أقل من 5 رموز احتياطية متبقية، الرجاء إنشاء وتخزين مجموعة جديدة قبل نفاد الرموز لتجنب إغلاق حسابك.',
|
||||
'mfa_option_totp_title' => 'تطبيق الجوال',
|
||||
'mfa_option_totp_desc' => 'لاستخدام المصادقة المتعددة العوامل، ستحتاج إلى تطبيق محمول يدعم TOTP مثل Google Authenticator أو Authy أو Microsoft Authenticer.',
|
||||
'mfa_option_totp_desc' => 'لاستخدام المصادقة المتعددة العوامل، ستحتاج إلى تطبيق جوال يدعم كلمة السر المؤقته -TOTP- مثل جوجل أوثنتيكاتور -Google Authenticator- أو أوثي -Authy- أو مايكروسوفت أوثنتيكاتور -Microsoft Authenticator-.',
|
||||
'mfa_option_backup_codes_title' => 'رموز النسخ الاحتياطي',
|
||||
'mfa_option_backup_codes_desc' => 'Generates a set of one-time-use backup codes which you\'ll enter on login to verify your identity. Make sure to store these in a safe & secure place.',
|
||||
'mfa_gen_confirm_and_enable' => 'Confirm and Enable',
|
||||
'mfa_gen_backup_codes_title' => 'Backup Codes Setup',
|
||||
'mfa_gen_backup_codes_desc' => 'Store the below list of codes in a safe place. When accessing the system you\'ll be able to use one of the codes as a second authentication mechanism.',
|
||||
'mfa_gen_backup_codes_download' => 'Download Codes',
|
||||
'mfa_gen_backup_codes_usage_warning' => 'Each code can only be used once',
|
||||
'mfa_gen_totp_title' => 'Mobile App Setup',
|
||||
'mfa_gen_totp_desc' => 'To use multi-factor authentication you\'ll need a mobile application that supports TOTP such as Google Authenticator, Authy or Microsoft Authenticator.',
|
||||
'mfa_gen_totp_scan' => 'Scan the QR code below using your preferred authentication app to get started.',
|
||||
'mfa_gen_totp_verify_setup' => 'Verify Setup',
|
||||
'mfa_gen_totp_verify_setup_desc' => 'Verify that all is working by entering a code, generated within your authentication app, in the input box below:',
|
||||
'mfa_gen_totp_provide_code_here' => 'Provide your app generated code here',
|
||||
'mfa_verify_access' => 'Verify Access',
|
||||
'mfa_verify_access_desc' => 'Your user account requires you to confirm your identity via an additional level of verification before you\'re granted access. Verify using one of your configured methods to continue.',
|
||||
'mfa_verify_no_methods' => 'No Methods Configured',
|
||||
'mfa_verify_no_methods_desc' => 'No multi-factor authentication methods could be found for your account. You\'ll need to set up at least one method before you gain access.',
|
||||
'mfa_verify_use_totp' => 'Verify using a mobile app',
|
||||
'mfa_verify_use_backup_codes' => 'Verify using a backup code',
|
||||
'mfa_verify_backup_code' => 'Backup Code',
|
||||
'mfa_verify_backup_code_desc' => 'Enter one of your remaining backup codes below:',
|
||||
'mfa_verify_backup_code_enter_here' => 'Enter backup code here',
|
||||
'mfa_verify_totp_desc' => 'Enter the code, generated using your mobile app, below:',
|
||||
'mfa_setup_login_notification' => 'Multi-factor method configured, Please now login again using the configured method.',
|
||||
'mfa_option_backup_codes_desc' => 'إنشاء مجموعة من رموز النسخ الاحتياطية للاستخدام مرة واحدة و التي سَتُدِخلها عند تسجيل الدخول للتحقق من هويتك. احرص أن تخزينها في مكان آمن.',
|
||||
'mfa_gen_confirm_and_enable' => 'تأكيد وتمكين',
|
||||
'mfa_gen_backup_codes_title' => 'إعداد رموز النسخ الاحتياطي',
|
||||
'mfa_gen_backup_codes_desc' => 'خَزِن قائمة الرموز أدناه في مكان آمن. عند الوصول إلى النظام، ستتمكن من استخدام أحد الرموز كآلية مصادقة ثانية.',
|
||||
'mfa_gen_backup_codes_download' => 'تنزيل الرموز',
|
||||
'mfa_gen_backup_codes_usage_warning' => 'يمكن استخدام كل رمز مرة واحدة فقط',
|
||||
'mfa_gen_totp_title' => 'إعداد تطبيق الجوال',
|
||||
'mfa_gen_totp_desc' => 'لاستخدام المصادقة المتعددة ، ستحتاج إلى تطبيق جوال كلمة السر المؤقته -TOTP- مثل جوجل أوثنتيكاتور -Google Authenticator- أو أوثي -Authy- أو مايكروسوفت أوثنتيكاتور -Microsoft Authenticator-.',
|
||||
'mfa_gen_totp_scan' => 'امسح رمز الاستجابة السريعة -QR- أدناه باستخدام تطبيق المصادقة المفضل لديك للبدء.',
|
||||
'mfa_gen_totp_verify_setup' => 'التحقق من الإعداد',
|
||||
'mfa_gen_totp_verify_setup_desc' => 'تحقق أن كل شيء يعمل عن طريق إدخال رمز تم إنشاؤه داخل تطبيق المصادقة الخاص بك في مربع الإدخال أدناه:',
|
||||
'mfa_gen_totp_provide_code_here' => 'أدخل الرمز الذي تم إنشاؤه للتطبيق الخاص بك هنا',
|
||||
'mfa_verify_access' => 'التحقق من الوصول',
|
||||
'mfa_verify_access_desc' => 'يتطلب حساب المستخدم الخاص بك تأكيد هويتك عن طريق مستوى إضافي من التحقق قبل منحك حق الوصول. تحقق استخدام إحدى الطرق التي إعدادها للمتابعة.',
|
||||
'mfa_verify_no_methods' => 'لا توجد طرق معدة',
|
||||
'mfa_verify_no_methods_desc' => 'لم يتم العثور على طرق مصادقة متعددة العوامل لحسابك. ستحتاج إلى إعداد طريقة واحدة على الأقل قبل أن تتمكن من الوصول.',
|
||||
'mfa_verify_use_totp' => 'التحقق باستخدام تطبيق الجوال',
|
||||
'mfa_verify_use_backup_codes' => 'التحقق باستخدام رمز النسخ الاحتياطي',
|
||||
'mfa_verify_backup_code' => 'الرموز الاحتياطية',
|
||||
'mfa_verify_backup_code_desc' => 'أدخل أحد الرموز الاحتياطية المتبقية أدناه:',
|
||||
'mfa_verify_backup_code_enter_here' => 'أدخل الرمز الاحتياطي هنا',
|
||||
'mfa_verify_totp_desc' => 'أدخل الرمز الذي تم إنشاؤه باستخدام تطبيق الجوال الخاص بك، أدناه:',
|
||||
'mfa_setup_login_notification' => 'تم إعداد طريقة الدخول متعددة العوامل، يرجى الآن تسجيل الدخول مرة أخرى باستخدام الطريقة التي تم إعدادها.',
|
||||
];
|
||||
|
||||
@@ -20,7 +20,7 @@ return [
|
||||
'description' => 'الوصف',
|
||||
'role' => 'الدور',
|
||||
'cover_image' => 'صورة الغلاف',
|
||||
'cover_image_description' => 'يجب أن يكون حجم هذه الصورة تقريبًا 440x250 بكسل، على الرغم من أنه سيتم تحجيمها وقصها بشكل مرن لتناسب واجهة المستخدم في سيناريوهات مختلفة حسب الحاجة، لذا فإن الأبعاد الفعلية للعرض ستختلف.',
|
||||
'cover_image_description' => 'يجب أن يكون حجم هذه الصورة تقريبًا 440 في 250 بكسل، مع أنّه سيتم تحجيمها وقصها بشكل مرن لتناسب واجهة المستخدم في سيناريوهات مختلفة حسب الحاجة، لذا فإن الأبعاد الفعلية للعرض ستختلف.',
|
||||
|
||||
// Actions
|
||||
'actions' => 'إجراءات',
|
||||
@@ -30,6 +30,8 @@ return [
|
||||
'create' => 'إنشاء',
|
||||
'update' => 'تحديث',
|
||||
'edit' => 'تعديل',
|
||||
'archive' => 'أرشف',
|
||||
'unarchive' => 'إلغاء الأرشفة',
|
||||
'sort' => 'سرد',
|
||||
'move' => 'نقل',
|
||||
'copy' => 'نسخ',
|
||||
@@ -48,8 +50,8 @@ return [
|
||||
'unfavourite' => 'إزالة من المفضلة',
|
||||
'next' => 'التالي',
|
||||
'previous' => 'السابق',
|
||||
'filter_active' => 'الفلاتر المفعلة:',
|
||||
'filter_clear' => 'مسح الفلاتر',
|
||||
'filter_active' => 'التصفية المفعلة:',
|
||||
'filter_clear' => 'مسح التصفية',
|
||||
'download' => 'تنزيل',
|
||||
'open_in_tab' => 'فتح في علامة تبويب',
|
||||
'open' => 'فتح',
|
||||
@@ -109,5 +111,5 @@ return [
|
||||
'terms_of_service' => 'اتفاقية شروط الخدمة',
|
||||
|
||||
// OpenSearch
|
||||
'opensearch_description' => 'Search :appName',
|
||||
'opensearch_description' => 'البحث عن :appName',
|
||||
];
|
||||
|
||||
@@ -6,36 +6,36 @@ return [
|
||||
|
||||
// Image Manager
|
||||
'image_select' => 'تحديد صورة',
|
||||
'image_list' => 'Image List',
|
||||
'image_details' => 'Image Details',
|
||||
'image_upload' => 'Upload Image',
|
||||
'image_intro' => 'Here you can select and manage images that have been previously uploaded to the system.',
|
||||
'image_intro_upload' => 'Upload a new image by dragging an image file into this window, or by using the "Upload Image" button above.',
|
||||
'image_list' => 'قائمة الصور',
|
||||
'image_details' => 'تفاصيل الصورة',
|
||||
'image_upload' => 'تحميل صورة',
|
||||
'image_intro' => 'هنا يمكنك تحديد وإدارة الصور التي تم تحميلها مسبقًا إلى النظام.',
|
||||
'image_intro_upload' => 'تحميل صورة جديدة عن طريق سحب الصورة إلى هذه النافذة، أو باستخدام زر "تحميل صورة" أعلاه.',
|
||||
'image_all' => 'الكل',
|
||||
'image_all_title' => 'عرض جميع الصور',
|
||||
'image_book_title' => 'عرض الصور المرفوعة لهذا الكتاب',
|
||||
'image_page_title' => 'عرض الصور المرفوعة لهذه الصفحة',
|
||||
'image_search_hint' => 'البحث باستخدام اسم الصورة',
|
||||
'image_uploaded' => 'وقت الرفع :uploadedDate',
|
||||
'image_uploaded_by' => 'Uploaded by :userName',
|
||||
'image_uploaded_to' => 'Uploaded to :pageLink',
|
||||
'image_updated' => 'Updated :updateDate',
|
||||
'image_uploaded_by' => 'تم تحميلها من قبل :userName',
|
||||
'image_uploaded_to' => 'تم رفعها إلى :pageLink',
|
||||
'image_updated' => 'تم تحديثها :updatedate',
|
||||
'image_load_more' => 'المزيد',
|
||||
'image_image_name' => 'اسم الصورة',
|
||||
'image_delete_used' => 'هذه الصورة مستخدمة بالصفحات أدناه.',
|
||||
'image_delete_confirm_text' => 'هل أنت متأكد من أنك تريد حذف هذه الصورة؟',
|
||||
'image_select_image' => 'تحديد الصورة',
|
||||
'image_dropzone' => 'قم بإسقاط الصورة أو اضغط هنا للرفع',
|
||||
'image_dropzone_drop' => 'Drop images here to upload',
|
||||
'image_dropzone_drop' => 'إسقاط صورة أو اضغط هنا للرفع',
|
||||
'images_deleted' => 'تم حذف الصور',
|
||||
'image_preview' => 'معاينة الصور',
|
||||
'image_upload_success' => 'تم رفع الصورة بنجاح',
|
||||
'image_update_success' => 'تم تحديث تفاصيل الصورة بنجاح',
|
||||
'image_delete_success' => 'تم حذف الصورة بنجاح',
|
||||
'image_replace' => 'Replace Image',
|
||||
'image_replace_success' => 'Image file successfully updated',
|
||||
'image_rebuild_thumbs' => 'Regenerate Size Variations',
|
||||
'image_rebuild_thumbs_success' => 'Image size variations successfully rebuilt!',
|
||||
'image_replace' => 'استبدال صورة',
|
||||
'image_replace_success' => 'تم تحديث الصورة بنجاح',
|
||||
'image_rebuild_thumbs' => 'تجديد تغيرات الحجم',
|
||||
'image_rebuild_thumbs_success' => 'تم إعادة بناء تغيرات حجم الصورة بنجاح!',
|
||||
|
||||
// Code Editor
|
||||
'code_editor' => 'تعديل الشفرة',
|
||||
|
||||
@@ -13,7 +13,7 @@ return [
|
||||
'cancel' => 'إلغاء',
|
||||
'save' => 'حفظ',
|
||||
'close' => 'إغلاق',
|
||||
'apply' => 'Apply',
|
||||
'apply' => 'تطبيق',
|
||||
'undo' => 'تراجع',
|
||||
'redo' => 'إعادة التنفيذ',
|
||||
'left' => 'يسار',
|
||||
@@ -25,7 +25,7 @@ return [
|
||||
'width' => 'العرض',
|
||||
'height' => 'الارتفاع',
|
||||
'More' => 'المزيد',
|
||||
'select' => 'Select...',
|
||||
'select' => 'إختار...',
|
||||
|
||||
// Toolbar
|
||||
'formats' => 'التنسيقات',
|
||||
@@ -48,79 +48,80 @@ return [
|
||||
'superscript' => 'نص مرتفع',
|
||||
'subscript' => 'نص منخفض',
|
||||
'text_color' => 'لون النص',
|
||||
'highlight_color' => 'لون التمييز',
|
||||
'custom_color' => 'لون مخصص',
|
||||
'remove_color' => 'إزالة اللون',
|
||||
'background_color' => 'لون الخلفية',
|
||||
'align_left' => 'محاذاة لليسار',
|
||||
'align_center' => 'محاذاة بالمنتصف',
|
||||
'align_right' => 'مُحاذاة لليمين',
|
||||
'align_justify' => 'Justify',
|
||||
'align_justify' => 'المحاذاة',
|
||||
'list_bullet' => 'قائمة نقاط',
|
||||
'list_numbered' => 'قائمة مرقمة',
|
||||
'list_task' => 'Task list',
|
||||
'list_task' => 'قائمة المهام',
|
||||
'indent_increase' => 'زيادة البادئة',
|
||||
'indent_decrease' => 'إنقاص البادئة',
|
||||
'table' => 'جدول',
|
||||
'insert_image' => 'ادراج صورة',
|
||||
'insert_image_title' => 'Insert/Edit Image',
|
||||
'insert_link' => 'Insert/edit link',
|
||||
'insert_link_title' => 'Insert/Edit Link',
|
||||
'insert_horizontal_line' => 'Insert horizontal line',
|
||||
'insert_code_block' => 'Insert code block',
|
||||
'edit_code_block' => 'Edit code block',
|
||||
'insert_drawing' => 'Insert/edit drawing',
|
||||
'drawing_manager' => 'Drawing manager',
|
||||
'insert_media' => 'Insert/edit media',
|
||||
'insert_media_title' => 'Insert/Edit Media',
|
||||
'clear_formatting' => 'Clear formatting',
|
||||
'source_code' => 'Source code',
|
||||
'source_code_title' => 'Source Code',
|
||||
'fullscreen' => 'Fullscreen',
|
||||
'image_options' => 'Image options',
|
||||
'insert_image_title' => 'إضافة/تحرير الصورة',
|
||||
'insert_link' => 'إضافة/تعديل الرابط',
|
||||
'insert_link_title' => 'إضافة/تحرير الرابط',
|
||||
'insert_horizontal_line' => 'إضافة خط أفقي',
|
||||
'insert_code_block' => 'إضافة مربع رموز برمجية',
|
||||
'edit_code_block' => 'تعديل مربع الرموز البرمجية',
|
||||
'insert_drawing' => 'إضافة/تعديل الرسم',
|
||||
'drawing_manager' => 'إدارة الرسم',
|
||||
'insert_media' => 'إضافة/تحرير الوسائط',
|
||||
'insert_media_title' => 'إضافة/تحرير الوسائط',
|
||||
'clear_formatting' => 'مسح التنسيق',
|
||||
'source_code' => 'الرمز البرمجي',
|
||||
'source_code_title' => 'الرمز البرمجي',
|
||||
'fullscreen' => 'شاشة كاملة',
|
||||
'image_options' => 'خيارات الصورة',
|
||||
|
||||
// Tables
|
||||
'table_properties' => 'Table properties',
|
||||
'table_properties_title' => 'Table Properties',
|
||||
'delete_table' => 'Delete table',
|
||||
'table_clear_formatting' => 'Clear table formatting',
|
||||
'resize_to_contents' => 'Resize to contents',
|
||||
'row_header' => 'Row header',
|
||||
'insert_row_before' => 'Insert row before',
|
||||
'insert_row_after' => 'Insert row after',
|
||||
'delete_row' => 'Delete row',
|
||||
'insert_column_before' => 'Insert column before',
|
||||
'insert_column_after' => 'Insert column after',
|
||||
'delete_column' => 'Delete column',
|
||||
'table_cell' => 'Cell',
|
||||
'table_row' => 'Row',
|
||||
'table_column' => 'Column',
|
||||
'cell_properties' => 'Cell properties',
|
||||
'cell_properties_title' => 'Cell Properties',
|
||||
'cell_type' => 'Cell type',
|
||||
'cell_type_cell' => 'Cell',
|
||||
'cell_scope' => 'Scope',
|
||||
'cell_type_header' => 'Header cell',
|
||||
'merge_cells' => 'Merge cells',
|
||||
'split_cell' => 'Split cell',
|
||||
'table_row_group' => 'Row Group',
|
||||
'table_column_group' => 'Column Group',
|
||||
'horizontal_align' => 'Horizontal align',
|
||||
'vertical_align' => 'Vertical align',
|
||||
'border_width' => 'Border width',
|
||||
'border_style' => 'Border style',
|
||||
'border_color' => 'Border color',
|
||||
'row_properties' => 'Row properties',
|
||||
'row_properties_title' => 'Row Properties',
|
||||
'cut_row' => 'Cut row',
|
||||
'copy_row' => 'Copy row',
|
||||
'paste_row_before' => 'Paste row before',
|
||||
'paste_row_after' => 'Paste row after',
|
||||
'row_type' => 'Row type',
|
||||
'row_type_header' => 'Header',
|
||||
'row_type_body' => 'Body',
|
||||
'row_type_footer' => 'Footer',
|
||||
'alignment' => 'Alignment',
|
||||
'cut_column' => 'Cut column',
|
||||
'table_properties' => 'خصائص الجدول',
|
||||
'table_properties_title' => 'خصائص الجدول',
|
||||
'delete_table' => 'حذف الجدول',
|
||||
'table_clear_formatting' => 'مسح تنسيق الجدول',
|
||||
'resize_to_contents' => 'تغيير الحجم إلى المحتوى',
|
||||
'row_header' => 'رأس الصف',
|
||||
'insert_row_before' => 'إضافة صف قبل',
|
||||
'insert_row_after' => 'إضافة صف بعد',
|
||||
'delete_row' => 'حذف الصف',
|
||||
'insert_column_before' => 'إدراج عمود قبل',
|
||||
'insert_column_after' => 'إدراج عمود بعد',
|
||||
'delete_column' => 'حذف عمود',
|
||||
'table_cell' => 'خلية',
|
||||
'table_row' => 'صف',
|
||||
'table_column' => 'عمود',
|
||||
'cell_properties' => 'خصائص الخلية',
|
||||
'cell_properties_title' => 'خصائص الخلية',
|
||||
'cell_type' => 'نوع الخلية',
|
||||
'cell_type_cell' => 'الخلية',
|
||||
'cell_scope' => 'النِطَاق',
|
||||
'cell_type_header' => 'عنوان الخلية',
|
||||
'merge_cells' => 'دمج الخلايا',
|
||||
'split_cell' => 'خلية منقسمة',
|
||||
'table_row_group' => 'مجموعة الصفوف',
|
||||
'table_column_group' => 'مجموعة الأعمدة',
|
||||
'horizontal_align' => 'محاذاة أفقية',
|
||||
'vertical_align' => 'محاذاة عمودية',
|
||||
'border_width' => 'عرض الحدود',
|
||||
'border_style' => 'نمط الحدود',
|
||||
'border_color' => 'لون الحدود',
|
||||
'row_properties' => 'خصائص الصف',
|
||||
'row_properties_title' => 'خصائص الصف',
|
||||
'cut_row' => 'فص الصف',
|
||||
'copy_row' => 'نسخ الصف',
|
||||
'paste_row_before' => 'لصق الصف قبل',
|
||||
'paste_row_after' => 'لصق الصف بعد',
|
||||
'row_type' => 'نوع الصف',
|
||||
'row_type_header' => 'العنوان',
|
||||
'row_type_body' => 'المحتوى ',
|
||||
'row_type_footer' => 'تذييل',
|
||||
'alignment' => 'المحاذاة',
|
||||
'cut_column' => 'قص العمود',
|
||||
'copy_column' => 'نسخ العمود',
|
||||
'paste_column_before' => 'لصق عمود قبل',
|
||||
'paste_column_after' => 'لصق عمود بعد',
|
||||
@@ -128,54 +129,54 @@ return [
|
||||
'cell_spacing' => 'تباعد الخلايا',
|
||||
'caption' => 'الوصف',
|
||||
'show_caption' => 'إظهار الوصف',
|
||||
'constrain' => 'Constrain proportions',
|
||||
'cell_border_solid' => 'Solid',
|
||||
'cell_border_dotted' => 'Dotted',
|
||||
'cell_border_dashed' => 'Dashed',
|
||||
'cell_border_double' => 'Double',
|
||||
'cell_border_groove' => 'Groove',
|
||||
'cell_border_ridge' => 'Ridge',
|
||||
'cell_border_inset' => 'Inset',
|
||||
'cell_border_outset' => 'Outset',
|
||||
'cell_border_none' => 'None',
|
||||
'cell_border_hidden' => 'Hidden',
|
||||
'constrain' => 'تقييد النسب',
|
||||
'cell_border_solid' => 'لون كامل',
|
||||
'cell_border_dotted' => 'مُنَقط',
|
||||
'cell_border_dashed' => 'متقطع',
|
||||
'cell_border_double' => 'مزدوج',
|
||||
'cell_border_groove' => 'أخدود',
|
||||
'cell_border_ridge' => 'الحافَة',
|
||||
'cell_border_inset' => 'الداخلية',
|
||||
'cell_border_outset' => 'الخارجية',
|
||||
'cell_border_none' => 'لا شَيْء',
|
||||
'cell_border_hidden' => 'مخفي',
|
||||
|
||||
// Images, links, details/summary & embed
|
||||
'source' => 'Source',
|
||||
'alt_desc' => 'Alternative description',
|
||||
'embed' => 'Embed',
|
||||
'paste_embed' => 'Paste your embed code below:',
|
||||
'url' => 'URL',
|
||||
'text_to_display' => 'Text to display',
|
||||
'title' => 'Title',
|
||||
'browse_links' => 'Browse links',
|
||||
'open_link' => 'Open link',
|
||||
'open_link_in' => 'Open link in...',
|
||||
'open_link_current' => 'Current window',
|
||||
'open_link_new' => 'New window',
|
||||
'remove_link' => 'Remove link',
|
||||
'insert_collapsible' => 'Insert collapsible block',
|
||||
'collapsible_unwrap' => 'Unwrap',
|
||||
'edit_label' => 'Edit label',
|
||||
'toggle_open_closed' => 'Toggle open/closed',
|
||||
'collapsible_edit' => 'Edit collapsible block',
|
||||
'toggle_label' => 'Toggle label',
|
||||
'source' => 'المصدر',
|
||||
'alt_desc' => 'وصف بديل',
|
||||
'embed' => 'تضمين',
|
||||
'paste_embed' => 'قم بلصق الرموز المصدرية المضمنة الخاص بك أدناه:',
|
||||
'url' => 'الرابط',
|
||||
'text_to_display' => 'النص المراد عرضه',
|
||||
'title' => 'العنوان',
|
||||
'browse_links' => 'تصفح الروابط',
|
||||
'open_link' => 'افتح الرابط',
|
||||
'open_link_in' => 'افتح الرابط في...',
|
||||
'open_link_current' => 'النافذة الحالية',
|
||||
'open_link_new' => 'نافذة جديدة',
|
||||
'remove_link' => 'إزالة الرابط',
|
||||
'insert_collapsible' => 'أدخل كتلة قابلة للطي',
|
||||
'collapsible_unwrap' => 'بسط',
|
||||
'edit_label' => 'عدل الوصف',
|
||||
'toggle_open_closed' => 'التبديل بين الفتح والإغلاق',
|
||||
'collapsible_edit' => 'تحرير الكتلة القابلة للطي',
|
||||
'toggle_label' => 'تبديل التسمية',
|
||||
|
||||
// About view
|
||||
'about' => 'About the editor',
|
||||
'about_title' => 'About the WYSIWYG Editor',
|
||||
'editor_license' => 'Editor License & Copyright',
|
||||
'editor_lexical_license' => 'This editor is built as a fork of :lexicalLink which is distributed under the MIT license.',
|
||||
'editor_lexical_license_link' => 'Full license details can be found here.',
|
||||
'editor_tiny_license' => 'This editor is built using :tinyLink which is provided under the MIT license.',
|
||||
'editor_tiny_license_link' => 'The copyright and license details of TinyMCE can be found here.',
|
||||
'save_continue' => 'Save Page & Continue',
|
||||
'callouts_cycle' => '(Keep pressing to toggle through types)',
|
||||
'link_selector' => 'Link to content',
|
||||
'shortcuts' => 'Shortcuts',
|
||||
'shortcut' => 'Shortcut',
|
||||
'shortcuts_intro' => 'The following shortcuts are available in the editor:',
|
||||
'windows_linux' => '(Windows/Linux)',
|
||||
'mac' => '(Mac)',
|
||||
'description' => 'Description',
|
||||
'about' => 'عن المحرر',
|
||||
'about_title' => 'حول محرر ما تراه هو ما تحصل عليه -WYSIWYG-',
|
||||
'editor_license' => 'رخصة المحرر وحقوق التأليف والنشر',
|
||||
'editor_lexical_license' => 'تم إنشاء هذا المحرر باعتباره فرعًا لـ :lexicalLink الذي يتم توزيعه بموجب ترخيص معهد ماساتشوستس للتقانة -MIT-.',
|
||||
'editor_lexical_license_link' => 'يمكنك العثور على تفاصيل الترخيص الكاملة هنا.',
|
||||
'editor_tiny_license' => 'تم إنشاء هذا المحرر باستخدام :tinyLink والذي يتم توفيره بموجب ترخيص معهد ماساتشوستس للتقانة -MIT-.',
|
||||
'editor_tiny_license_link' => 'يمكن الاطلاع هنا على تفاصيل حقوق التأليف والنشر والترخيص الخاصة بتاینیامسیای -TinyMCE-.',
|
||||
'save_continue' => 'حفظ الصفحة ومتابعة',
|
||||
'callouts_cycle' => '(استمر في الضغط للتبديل بين الأنواع)',
|
||||
'link_selector' => 'رابط للمحتوى',
|
||||
'shortcuts' => 'الاختصارات',
|
||||
'shortcut' => 'الاختصار',
|
||||
'shortcuts_intro' => 'الاختصارات التالية متاحة في المحرر:',
|
||||
'windows_linux' => '(ويندوز/لينكس)',
|
||||
'mac' => '(ماك)',
|
||||
'description' => 'الوصف',
|
||||
];
|
||||
|
||||
@@ -22,15 +22,15 @@ return [
|
||||
'meta_created_name' => 'أنشئ :timeLength بواسطة :user',
|
||||
'meta_updated' => 'مُحدث :timeLength',
|
||||
'meta_updated_name' => 'مُحدث :timeLength بواسطة :user',
|
||||
'meta_owned_name' => 'Owned by :user',
|
||||
'meta_reference_count' => 'Referenced by :count item|Referenced by :count items',
|
||||
'meta_owned_name' => 'مملوكة لـ:user',
|
||||
'meta_reference_count' => 'مشار إليه :count مرة|مشار إليه :count مرة',
|
||||
'entity_select' => 'اختيار الكيان',
|
||||
'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item',
|
||||
'entity_select_lack_permission' => 'ليس لديك الصلاحيات المطلوبة لتحديد هذا العنصر',
|
||||
'images' => 'صور',
|
||||
'my_recent_drafts' => 'مسوداتي الحديثة',
|
||||
'my_recently_viewed' => 'ما عرضته مؤخراً',
|
||||
'my_most_viewed_favourites' => 'My Most Viewed Favourites',
|
||||
'my_favourites' => 'My Favourites',
|
||||
'my_most_viewed_favourites' => 'مفضلاتي الأكثر مشاهدة',
|
||||
'my_favourites' => 'مفضلاتي',
|
||||
'no_pages_viewed' => 'لم تستعرض أي صفحات',
|
||||
'no_pages_recently_created' => 'لم تنشأ أي صفحات مؤخراً',
|
||||
'no_pages_recently_updated' => 'لم تُحدّث أي صفحات مؤخراً',
|
||||
@@ -38,43 +38,43 @@ return [
|
||||
'export_html' => 'صفحة ويب',
|
||||
'export_pdf' => 'ملف PDF',
|
||||
'export_text' => 'ملف نص عادي',
|
||||
'export_md' => 'Markdown File',
|
||||
'export_zip' => 'Portable ZIP',
|
||||
'default_template' => 'Default Page Template',
|
||||
'default_template_explain' => 'Assign a page template that will be used as the default content for all pages created within this item. Keep in mind this will only be used if the page creator has view access to the chosen template page.',
|
||||
'default_template_select' => 'Select a template page',
|
||||
'import' => 'Import',
|
||||
'import_validate' => 'Validate Import',
|
||||
'import_desc' => 'Import books, chapters & pages using a portable zip export from the same, or a different, instance. Select a ZIP file to proceed. After the file has been uploaded and validated you\'ll be able to configure & confirm the import in the next view.',
|
||||
'import_zip_select' => 'Select ZIP file to upload',
|
||||
'import_zip_validation_errors' => 'Errors were detected while validating the provided ZIP file:',
|
||||
'import_pending' => 'Pending Imports',
|
||||
'import_pending_none' => 'No imports have been started.',
|
||||
'import_continue' => 'Continue Import',
|
||||
'import_continue_desc' => 'Review the content due to be imported from the uploaded ZIP file. When ready, run the import to add its contents to this system. The uploaded ZIP import file will be automatically removed on successful import.',
|
||||
'import_details' => 'Import Details',
|
||||
'import_run' => 'Run Import',
|
||||
'import_size' => ':size Import ZIP Size',
|
||||
'import_uploaded_at' => 'Uploaded :relativeTime',
|
||||
'import_uploaded_by' => 'Uploaded by',
|
||||
'import_location' => 'Import Location',
|
||||
'import_location_desc' => 'Select a target location for your imported content. You\'ll need the relevant permissions to create within the location you choose.',
|
||||
'import_delete_confirm' => 'Are you sure you want to delete this import?',
|
||||
'import_delete_desc' => 'This will delete the uploaded import ZIP file, and cannot be undone.',
|
||||
'import_errors' => 'Import Errors',
|
||||
'import_errors_desc' => 'The follow errors occurred during the import attempt:',
|
||||
'export_md' => 'ملف ماركداون -Markdown-',
|
||||
'export_zip' => 'ملف مضغوط -ZIP-',
|
||||
'default_template' => 'قالب الصفحة الافتراضية',
|
||||
'default_template_explain' => 'قم بتعيين قالب صفحة سيتم استخدامه كمحتوى افتراضي لجميع الصفحات التي تم إنشاؤها ضمن هذا العنصر. ضع في اعتبارك أن هذا لن يتم استخدامه إلا إذا كان لدى منشئ الصفحة حق الوصول إلى صفحة القالب المختارة.',
|
||||
'default_template_select' => 'حدد صفحة القالب',
|
||||
'import' => 'استيراد',
|
||||
'import_validate' => 'التحقق من صحة الاستيراد',
|
||||
'import_desc' => 'استيراد الكتب والفصول والصفحات باستخدام تصدير مِلَفّ مضغوط ZIP محمول من نفس النظام أو نظام مختلف. حدد مِلَفّ ZIP للمتابعة. بعد تحميل المِلَفّ والتحقق من صحته، ستتمكن من إعداد وتأكيد الاستيراد في العرض التالي.',
|
||||
'import_zip_select' => 'حدد مِلَفّ مضغوط بصيغة ZIP للتحميل',
|
||||
'import_zip_validation_errors' => 'تم اكتشاف أخطاء في أثناء التحقق من صحة المِلَفّ المضغوط ZIP المقدم:',
|
||||
'import_pending' => 'الاستيرادات المعلقة',
|
||||
'import_pending_none' => 'لم يتم البَدْء في أي عملية استيراد.',
|
||||
'import_continue' => 'متابعة الاستيراد',
|
||||
'import_continue_desc' => 'راجع المحتوى الذي يجب استيراده من المِلَفّ المضغوط ZIP الذي تم تحميله. عندما يكون جاهزًا، تشتغل عملية الاستيراد لإضافة محتوياته إلى هذا النظام. سيتم إزالة مِلَفّ الاستيراد الذي تم تحميله تلقائيًا عند الاستيراد الناجح.',
|
||||
'import_details' => 'تفاصيل الاستيراد',
|
||||
'import_run' => 'تشغيل الاستيراد',
|
||||
'import_size' => 'حجم الاستيراد :size ',
|
||||
'import_uploaded_at' => 'تم تحميلة في :relativeTime',
|
||||
'import_uploaded_by' => 'رُفِع بواسطة',
|
||||
'import_location' => 'موقع الاستيراد',
|
||||
'import_location_desc' => 'حدد موقعًا مستهدفًا للمحتوى المستورد. ستحتاج إلى الصلاحيات ذات الصلة لإنشاء المحتوى داخل الموقع الذي تختاره.',
|
||||
'import_delete_confirm' => 'متيقِّن من أنك تريد حذف الاستيراد؟',
|
||||
'import_delete_desc' => 'سيؤدي هذا إلى حذف مِلَفّ الاستيراد المضغوط ZIP، ولا يمكن التراجع عنه.',
|
||||
'import_errors' => 'أخطاء الاستيراد',
|
||||
'import_errors_desc' => 'حدثت الأخطاء التالية خلال محاولة الاستيراد:',
|
||||
|
||||
// Permissions and restrictions
|
||||
'permissions' => 'الأذونات',
|
||||
'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.',
|
||||
'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.',
|
||||
'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.',
|
||||
'permissions_desc' => 'تعيين الصلاحيات هنا لتجاوز الصلاحيات الافتراضية التي توفرها أدوار المستخدم.',
|
||||
'permissions_book_cascade' => 'سيتم نقل الصلاحيات التي تم تعيينها للكتب تلقائيًا إلى الفصول والصفحات الفرعية، ما لم تكن لديها صلاحيات خاصة بها محددة.',
|
||||
'permissions_chapter_cascade' => 'سيتم نقل الصلاحيات التي تم تعيينها على الفصول تلقائيًا إلى الصفحات الفرعية، ما لم تكن لديها صلاحيات خاصة بها محددة.',
|
||||
'permissions_save' => 'حفظ الأذونات',
|
||||
'permissions_owner' => 'Owner',
|
||||
'permissions_role_everyone_else' => 'Everyone Else',
|
||||
'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.',
|
||||
'permissions_role_override' => 'Override permissions for role',
|
||||
'permissions_inherit_defaults' => 'Inherit defaults',
|
||||
'permissions_owner' => 'المالك',
|
||||
'permissions_role_everyone_else' => 'الآخرين',
|
||||
'permissions_role_everyone_else_desc' => 'تعيين الصلاحيات لجميع الأدوار التي لم يتم تجاوزها على وجه التحديد.',
|
||||
'permissions_role_override' => 'تجاوز الصلاحيات للدور',
|
||||
'permissions_inherit_defaults' => 'وراثة الإعدادات الافتراضية',
|
||||
|
||||
// Search
|
||||
'search_results' => 'نتائج البحث',
|
||||
@@ -94,7 +94,7 @@ return [
|
||||
'search_permissions_set' => 'حزمة الأذونات',
|
||||
'search_created_by_me' => 'أنشئت بواسطتي',
|
||||
'search_updated_by_me' => 'حُدثت بواسطتي',
|
||||
'search_owned_by_me' => 'Owned by me',
|
||||
'search_owned_by_me' => 'مملوكة لي',
|
||||
'search_date_options' => 'خيارات التاريخ',
|
||||
'search_updated_before' => 'حدثت قبل',
|
||||
'search_updated_after' => 'حدثت بعد',
|
||||
@@ -117,24 +117,24 @@ return [
|
||||
'shelves_save' => 'حفظ الرف',
|
||||
'shelves_books' => 'كتب على هذا الرف',
|
||||
'shelves_add_books' => 'إضافة كتب لهذا الرف',
|
||||
'shelves_drag_books' => 'Drag books below to add them to this shelf',
|
||||
'shelves_drag_books' => 'اسحب الكتب الموجودة بالأسفل لإضافتها إلى هذا الرف',
|
||||
'shelves_empty_contents' => 'لا توجد كتب مخصصة لهذا الرف',
|
||||
'shelves_edit_and_assign' => 'تحرير الرف لإدراج كتب',
|
||||
'shelves_edit_named' => 'Edit Shelf :name',
|
||||
'shelves_edit' => 'Edit Shelf',
|
||||
'shelves_delete' => 'Delete Shelf',
|
||||
'shelves_delete_named' => 'Delete Shelf :name',
|
||||
'shelves_delete_explain' => "This will delete the shelf with the name ':name'. Contained books will not be deleted.",
|
||||
'shelves_delete_confirmation' => 'Are you sure you want to delete this shelf?',
|
||||
'shelves_permissions' => 'Shelf Permissions',
|
||||
'shelves_permissions_updated' => 'Shelf Permissions Updated',
|
||||
'shelves_permissions_active' => 'Shelf Permissions Active',
|
||||
'shelves_permissions_cascade_warning' => 'Permissions on shelves do not automatically cascade to contained books. This is because a book can exist on multiple shelves. Permissions can however be copied down to child books using the option found below.',
|
||||
'shelves_permissions_create' => 'Shelf create permissions are only used for copying permissions to child books using the action below. They do not control the ability to create books.',
|
||||
'shelves_edit_named' => 'تعديل الرف :name',
|
||||
'shelves_edit' => 'تعديل الرف',
|
||||
'shelves_delete' => 'حذف الرف',
|
||||
'shelves_delete_named' => 'حذف الرف :name',
|
||||
'shelves_delete_explain' => "سيؤدي هذا إلى حذف الرف الذي يحمل الاسم ':name'. لن يتم حذف الكتب المضمنة بداخله.",
|
||||
'shelves_delete_confirmation' => 'هل أنت متأكد أنك تريد حذف هذا الرف؟',
|
||||
'shelves_permissions' => 'صلاحيات الرف',
|
||||
'shelves_permissions_updated' => 'تم تحديث صلاحيات الرف',
|
||||
'shelves_permissions_active' => 'صلاحيات الرف نشطة',
|
||||
'shelves_permissions_cascade_warning' => 'لا يتم نقل الصلاحيات الموجودة على الأرفف تلقائيًا إلى الكتب الموجودة في كل رف. وذلك لأن الكتاب يمكن أن يوجد على أرفف متعددة. ومع ذلك، يمكن نسخ الصلاحيات إلى الكتب الفرعية باستخدام الخِيار الموجود أدناه.',
|
||||
'shelves_permissions_create' => 'تُستخدم صلاحيات إنشاء الرفوف فقط لنسخ الصلاحيات إلى الكتب الفرعية باستخدام الإجراء أدناه. ولا تتحكم في القدرة على إنشاء الكتب.',
|
||||
'shelves_copy_permissions_to_books' => 'نسخ أذونات الوصول إلى الكتب',
|
||||
'shelves_copy_permissions' => 'نسخ الأذونات',
|
||||
'shelves_copy_permissions_explain' => 'This will apply the current permission settings of this shelf to all books contained within. Before activating, ensure any changes to the permissions of this shelf have been saved.',
|
||||
'shelves_copy_permission_success' => 'Shelf permissions copied to :count books',
|
||||
'shelves_copy_permissions_explain' => 'سيؤدي هذا إلى تطبيق إعدادات الصلاحيات الحالية لهذا الرف على جميع الكتب الموجودة بداخله. قبل التنشيط، تأكد من حفظ أي تغييرات على صلاحيات هذا الرف.',
|
||||
'shelves_copy_permission_success' => 'تم نسخ صلاحيات الرف إلى :count كتاب/كتب',
|
||||
|
||||
// Books
|
||||
'book' => 'كتاب',
|
||||
@@ -166,9 +166,9 @@ return [
|
||||
'books_search_this' => 'البحث في هذا الكتاب',
|
||||
'books_navigation' => 'تصفح الكتاب',
|
||||
'books_sort' => 'فرز محتويات الكتاب',
|
||||
'books_sort_desc' => 'Move chapters and pages within a book to reorganise its contents. Other books can be added which allows easy moving of chapters and pages between books. Optionally an auto sort rule can be set to automatically sort this book\'s contents upon changes.',
|
||||
'books_sort_auto_sort' => 'Auto Sort Option',
|
||||
'books_sort_auto_sort_active' => 'Auto Sort Active: :sortName',
|
||||
'books_sort_desc' => 'نقل الفصول والصفحات داخل الكتاب لإعادة تنظيم محتوياته. يمكن إضافة كتب أخرى مما يسمح بنقل الفصول والصفحات بسهولة بين الكتب. اختياريًا، يمكن تعيين قاعدة فرز تلقائي لفرز محتويات هذا الكتاب تلقائيًا عند حدوث تغييرات.',
|
||||
'books_sort_auto_sort' => 'خِيار الفرز التلقائي',
|
||||
'books_sort_auto_sort_active' => 'الفرز التلقائي الشَغَّال: :sortName',
|
||||
'books_sort_named' => 'فرز كتاب :bookName',
|
||||
'books_sort_name' => 'ترتيب حسب الإسم',
|
||||
'books_sort_created' => 'ترتيب حسب تاريخ الإنشاء',
|
||||
@@ -177,19 +177,19 @@ return [
|
||||
'books_sort_chapters_last' => 'الفصول الأخيرة',
|
||||
'books_sort_show_other' => 'عرض كتب أخرى',
|
||||
'books_sort_save' => 'حفظ الترتيب الجديد',
|
||||
'books_sort_show_other_desc' => 'Add other books here to include them in the sort operation, and allow easy cross-book reorganisation.',
|
||||
'books_sort_move_up' => 'Move Up',
|
||||
'books_sort_move_down' => 'Move Down',
|
||||
'books_sort_move_prev_book' => 'Move to Previous Book',
|
||||
'books_sort_move_next_book' => 'Move to Next Book',
|
||||
'books_sort_move_prev_chapter' => 'Move Into Previous Chapter',
|
||||
'books_sort_move_next_chapter' => 'Move Into Next Chapter',
|
||||
'books_sort_move_book_start' => 'Move to Start of Book',
|
||||
'books_sort_move_book_end' => 'Move to End of Book',
|
||||
'books_sort_move_before_chapter' => 'Move to Before Chapter',
|
||||
'books_sort_move_after_chapter' => 'Move to After Chapter',
|
||||
'books_copy' => 'Copy Book',
|
||||
'books_copy_success' => 'Book successfully copied',
|
||||
'books_sort_show_other_desc' => 'أضف كتبًا أخرى هنا لتضمينها في عملية الفرز، والسماح بإعادة تنظيم الكتب بسهولة.',
|
||||
'books_sort_move_up' => 'حرك للأعلى',
|
||||
'books_sort_move_down' => 'حرك للأسفل',
|
||||
'books_sort_move_prev_book' => 'نقل للكتاب السابق',
|
||||
'books_sort_move_next_book' => 'نقل للكتاب التالي',
|
||||
'books_sort_move_prev_chapter' => 'نقل إلى الفصل السابق',
|
||||
'books_sort_move_next_chapter' => 'نقل إلى الفصل التالي',
|
||||
'books_sort_move_book_start' => 'نقل إلى بداية الكتاب',
|
||||
'books_sort_move_book_end' => 'نقل إلى نهاية الكتاب',
|
||||
'books_sort_move_before_chapter' => 'نقل إلى الفصل السابق',
|
||||
'books_sort_move_after_chapter' => 'نقل إلى الفصل التالي',
|
||||
'books_copy' => 'نسخة الكتاب',
|
||||
'books_copy_success' => 'تم نسخ الكتاب بنجاح',
|
||||
|
||||
// Chapters
|
||||
'chapter' => 'فصل',
|
||||
@@ -200,21 +200,21 @@ return [
|
||||
'chapters_create' => 'إنشاء فصل جديد',
|
||||
'chapters_delete' => 'حذف الفصل',
|
||||
'chapters_delete_named' => 'حذف فصل :chapterName',
|
||||
'chapters_delete_explain' => 'This will delete the chapter with the name \':chapterName\'. All pages that exist within this chapter will also be deleted.',
|
||||
'chapters_delete_explain' => 'سيؤدي هذا إلى حذف الفصل الذي يحمل الاسم \':chapterName\'. كما سيتم حذف جميع الصفحات الموجودة داخل هذا الفصل.',
|
||||
'chapters_delete_confirm' => 'تأكيد حذف الفصل؟',
|
||||
'chapters_edit' => 'تعديل الفصل',
|
||||
'chapters_edit_named' => 'تعديل فصل :chapterName',
|
||||
'chapters_save' => 'حفظ الفصل',
|
||||
'chapters_move' => 'نقل الفصل',
|
||||
'chapters_move_named' => 'نقل فصل :chapterName',
|
||||
'chapters_copy' => 'Copy Chapter',
|
||||
'chapters_copy_success' => 'Chapter successfully copied',
|
||||
'chapters_copy' => 'نسخ الفصل',
|
||||
'chapters_copy_success' => 'تم نسخ الفصل بنجاح',
|
||||
'chapters_permissions' => 'أذونات الفصل',
|
||||
'chapters_empty' => 'لا توجد أي صفحات في هذا الفصل حالياً',
|
||||
'chapters_permissions_active' => 'أذونات الفصل مفعلة',
|
||||
'chapters_permissions_success' => 'تم تحديث أذونات الفصل',
|
||||
'chapters_search_this' => 'البحث في هذا الفصل',
|
||||
'chapter_sort_book' => 'Sort Book',
|
||||
'chapter_sort_book' => 'فرز الكتاب',
|
||||
|
||||
// Pages
|
||||
'page' => 'صفحة',
|
||||
@@ -230,7 +230,7 @@ return [
|
||||
'pages_delete_draft' => 'حذف المسودة',
|
||||
'pages_delete_success' => 'تم حذف الصفحة',
|
||||
'pages_delete_draft_success' => 'تم حذف المسودة',
|
||||
'pages_delete_warning_template' => 'This page is in active use as a book or chapter default page template. These books or chapters will no longer have a default page template assigned after this page is deleted.',
|
||||
'pages_delete_warning_template' => 'هذه الصفحة قيد الاستخدام كقالب افتراضي لصفحات الكتب أو الفصول. لن يكون لهذه الكتب أو الفصول قالب افتراضي بعد حذفها.',
|
||||
'pages_delete_confirm' => 'تأكيد حذف الصفحة؟',
|
||||
'pages_delete_draft_confirm' => 'تأكيد حذف المسودة؟',
|
||||
'pages_editing_named' => ':pageName قيد التعديل',
|
||||
@@ -241,23 +241,23 @@ return [
|
||||
'pages_editing_page' => 'الصفحة قيد التعديل',
|
||||
'pages_edit_draft_save_at' => 'تم خفظ المسودة في ',
|
||||
'pages_edit_delete_draft' => 'حذف المسودة',
|
||||
'pages_edit_delete_draft_confirm' => 'Are you sure you want to delete your draft page changes? All of your changes, since the last full save, will be lost and the editor will be updated with the latest page non-draft save state.',
|
||||
'pages_edit_delete_draft_confirm' => 'متيقِّن من رغبتك في حذف تغييرات صفحة المُسَوَّدَة؟ ستُفقد جميع تغييراتك، منذ آخر حفظ كامل، وسيتم تحديث المحرر بأحدث حالة حفظ للصفحة (غير مسودة).',
|
||||
'pages_edit_discard_draft' => 'التخلص من المسودة',
|
||||
'pages_edit_switch_to_markdown' => 'Switch to Markdown Editor',
|
||||
'pages_edit_switch_to_markdown_clean' => '(Clean Content)',
|
||||
'pages_edit_switch_to_markdown_stable' => '(Stable Content)',
|
||||
'pages_edit_switch_to_wysiwyg' => 'Switch to WYSIWYG Editor',
|
||||
'pages_edit_switch_to_new_wysiwyg' => 'Switch to new WYSIWYG',
|
||||
'pages_edit_switch_to_new_wysiwyg_desc' => '(In Alpha Testing)',
|
||||
'pages_edit_switch_to_markdown' => 'التبديل إلى محرر ماركداون -Markdown-',
|
||||
'pages_edit_switch_to_markdown_clean' => '(محتوى نظيف)',
|
||||
'pages_edit_switch_to_markdown_stable' => '(محتوى مستقر)',
|
||||
'pages_edit_switch_to_wysiwyg' => 'التبديل إلى محرر ما تراه هو ما تحصل عليه -WYSIWYG-',
|
||||
'pages_edit_switch_to_new_wysiwyg' => 'التبديل إلى محرر ما تراه هو ما تحصل عليه الجديد -new WYSIWYG-',
|
||||
'pages_edit_switch_to_new_wysiwyg_desc' => '(في الاختبار التجريبي)',
|
||||
'pages_edit_set_changelog' => 'تثبيت سجل التعديل',
|
||||
'pages_edit_enter_changelog_desc' => 'ضع وصف مختصر للتعديلات التي تمت',
|
||||
'pages_edit_enter_changelog' => 'أدخل سجل التعديل',
|
||||
'pages_editor_switch_title' => 'Switch Editor',
|
||||
'pages_editor_switch_are_you_sure' => 'Are you sure you want to change the editor for this page?',
|
||||
'pages_editor_switch_consider_following' => 'Consider the following when changing editors:',
|
||||
'pages_editor_switch_consideration_a' => 'Once saved, the new editor option will be used by any future editors, including those that may not be able to change editor type themselves.',
|
||||
'pages_editor_switch_consideration_b' => 'This can potentially lead to a loss of detail and syntax in certain circumstances.',
|
||||
'pages_editor_switch_consideration_c' => 'Tag or changelog changes, made since last save, won\'t persist across this change.',
|
||||
'pages_editor_switch_title' => 'تبديل المحرر',
|
||||
'pages_editor_switch_are_you_sure' => 'متيقِّن أنك تريد تغيير المحرر لهذه الصفحة؟',
|
||||
'pages_editor_switch_consider_following' => 'عند تغيير المحررين، ضع في اعتبارك ما يلي:',
|
||||
'pages_editor_switch_consideration_a' => 'بمجرد الحفظ، سيتم استخدام خِيار المحرر الجديد بواسطة أي محررين مستقبليين، بما في ذلك أولئك الذين قد لا يتمكنون من تغيير نوع المحرر بأنفسهم.',
|
||||
'pages_editor_switch_consideration_b' => 'من الممكن أن يؤدي هذا إلى فقدان التفاصيل والنحو في ظروف معينة.',
|
||||
'pages_editor_switch_consideration_c' => 'لن تستمر تغييرات العلامة أو سجل التغييرات، التي تم إجراؤها منذ الحفظ الأخير، عبر هذا التغيير.',
|
||||
'pages_save' => 'حفظ الصفحة',
|
||||
'pages_title' => 'عنوان الصفحة',
|
||||
'pages_name' => 'اسم الصفحة',
|
||||
@@ -266,10 +266,11 @@ return [
|
||||
'pages_md_insert_image' => 'إدخال صورة',
|
||||
'pages_md_insert_link' => 'إدراج ارتباط الكيان',
|
||||
'pages_md_insert_drawing' => 'إدخال رسمة',
|
||||
'pages_md_show_preview' => 'Show preview',
|
||||
'pages_md_sync_scroll' => 'Sync preview scroll',
|
||||
'pages_drawing_unsaved' => 'Unsaved Drawing Found',
|
||||
'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?',
|
||||
'pages_md_show_preview' => 'عرض المعاينة',
|
||||
'pages_md_sync_scroll' => 'مزامنة معاينة التمرير',
|
||||
'pages_md_plain_editor' => 'محرر النصوص العادي',
|
||||
'pages_drawing_unsaved' => 'تم العثور على رسم غير محفوظ',
|
||||
'pages_drawing_unsaved_confirm' => 'تم العثور على بيانات رسم غير محفوظة من محاولة حفظ رسم سابقة فاشلة. هل ترغب في استعادة هذا الرسم غير المحفوظ ومواصلة تحريره؟',
|
||||
'pages_not_in_chapter' => 'صفحة ليست في فصل',
|
||||
'pages_move' => 'نقل الصفحة',
|
||||
'pages_copy' => 'نسخ الصفحة',
|
||||
@@ -279,17 +280,17 @@ return [
|
||||
'pages_permissions_success' => 'تم تحديث أذونات الصفحة',
|
||||
'pages_revision' => 'مراجعة',
|
||||
'pages_revisions' => 'مراجعات الصفحة',
|
||||
'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_desc' => 'تجد أدناه جميع الإصدارات السابقة لهذه الصفحة. يمكنك الاطلاع عليها ومقارنتها واستعادة الإصدارات القديمة إذا سمحت الصلاحيات بذلك. قد لا يظهر تاريخ الصفحة بالكامل هنا، إذ قد تُحذف الإصدارات القديمة تلقائيًا، وذلك حسب إعدادات النظام.',
|
||||
'pages_revisions_named' => 'مراجعات صفحة :pageName',
|
||||
'pages_revision_named' => 'مراجعة صفحة :pageName',
|
||||
'pages_revision_restored_from' => 'Restored from #:id; :summary',
|
||||
'pages_revision_restored_from' => 'تم الاستعادة من #:id; :summary',
|
||||
'pages_revisions_created_by' => 'أنشئ بواسطة',
|
||||
'pages_revisions_date' => 'تاريخ المراجعة',
|
||||
'pages_revisions_number' => '#',
|
||||
'pages_revisions_sort_number' => 'Revision Number',
|
||||
'pages_revisions_sort_number' => 'رَقْم المراجعة',
|
||||
'pages_revisions_numbered' => 'مراجعة #:id',
|
||||
'pages_revisions_numbered_changes' => 'مراجعة #: رقم تعريفي التغييرات',
|
||||
'pages_revisions_editor' => 'Editor Type',
|
||||
'pages_revisions_editor' => 'نوع المحرر',
|
||||
'pages_revisions_changelog' => 'سجل التعديل',
|
||||
'pages_revisions_changes' => 'التعديلات',
|
||||
'pages_revisions_current' => 'النسخة الحالية',
|
||||
@@ -297,20 +298,20 @@ return [
|
||||
'pages_revisions_restore' => 'استرجاع',
|
||||
'pages_revisions_none' => 'لا توجد مراجعات لهذه الصفحة',
|
||||
'pages_copy_link' => 'نسخ الرابط',
|
||||
'pages_edit_content_link' => 'Jump to section in editor',
|
||||
'pages_pointer_enter_mode' => 'Enter section select mode',
|
||||
'pages_pointer_label' => 'Page Section Options',
|
||||
'pages_pointer_permalink' => 'Page Section Permalink',
|
||||
'pages_pointer_include_tag' => 'Page Section Include Tag',
|
||||
'pages_pointer_toggle_link' => 'Permalink mode, Press to show include tag',
|
||||
'pages_pointer_toggle_include' => 'Include tag mode, Press to show permalink',
|
||||
'pages_edit_content_link' => 'انتقل إلى القسم في المحرر',
|
||||
'pages_pointer_enter_mode' => 'أدخل وضع اختيار القسم',
|
||||
'pages_pointer_label' => 'خيارات قسم الصفحة',
|
||||
'pages_pointer_permalink' => 'رابط دائم لقسم الصفحة',
|
||||
'pages_pointer_include_tag' => 'قسم الصفحة يتضمن العلامة',
|
||||
'pages_pointer_toggle_link' => 'وضع الرابط الدائم، اضغط لإظهار علامة التضمين',
|
||||
'pages_pointer_toggle_include' => 'تضمين وضع العلامة، اضغط لإظهار الرابط الدائم',
|
||||
'pages_permissions_active' => 'أذونات الصفحة مفعلة',
|
||||
'pages_initial_revision' => 'نشر مبدئي',
|
||||
'pages_references_update_revision' => 'System auto-update of internal links',
|
||||
'pages_references_update_revision' => 'التحديث التلقائي للنظام للروابط الداخلية',
|
||||
'pages_initial_name' => 'صفحة جديدة',
|
||||
'pages_editing_draft_notification' => 'جارٍ تعديل مسودة لم يتم حفظها من :timeDiff.',
|
||||
'pages_draft_edited_notification' => 'تم تحديث هذه الصفحة منذ ذلك الوقت. من الأفضل التخلص من هذه المسودة.',
|
||||
'pages_draft_page_changed_since_creation' => 'This page has been updated since this draft was created. It is recommended that you discard this draft or take care not to overwrite any page changes.',
|
||||
'pages_draft_page_changed_since_creation' => 'تم تحديث هذه الصفحة منذ إنشاء هذه المُسَوَّدَة. يُنصح بتجاهل هذه المُسَوَّدَة أو الحرص على عدم استبدال أي تغييرات في الصفحة.',
|
||||
'pages_draft_edit_active' => [
|
||||
'start_a' => ':count من المستخدمين بدأوا بتعديل هذه الصفحة',
|
||||
'start_b' => ':userName بدأ بتعديل هذه الصفحة',
|
||||
@@ -318,44 +319,44 @@ return [
|
||||
'time_b' => 'في آخر :minCount دقيقة/دقائق',
|
||||
'message' => 'وقت البدء: احرص على عدم الكتابة فوق تحديثات بعضنا البعض!',
|
||||
],
|
||||
'pages_draft_discarded' => 'Draft discarded! The editor has been updated with the current page content',
|
||||
'pages_draft_deleted' => 'Draft deleted! The editor has been updated with the current page content',
|
||||
'pages_draft_discarded' => 'تم رفض المُسَوَّدَة! تم تحديث المحرر بمحتوى الصفحة الحالي.',
|
||||
'pages_draft_deleted' => 'تم حذف المُسَوَّدَة! تم تحديث المحرر بمحتوى الصفحة الحالي.',
|
||||
'pages_specific' => 'صفحة محددة',
|
||||
'pages_is_template' => 'قالب الصفحة',
|
||||
|
||||
// Editor Sidebar
|
||||
'toggle_sidebar' => 'Toggle Sidebar',
|
||||
'toggle_sidebar' => 'تبديل الشريط الجانبي',
|
||||
'page_tags' => 'وسوم الصفحة',
|
||||
'chapter_tags' => 'وسوم الفصل',
|
||||
'book_tags' => 'وسوم الكتاب',
|
||||
'shelf_tags' => 'علامات الرف',
|
||||
'tag' => 'وسم',
|
||||
'tags' => 'وسوم',
|
||||
'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.',
|
||||
'tags_index_desc' => 'يمكن تطبيق الوسوم على المحتوى داخل النظام لتطبيق تصنيف مرن. يمكن أن تحتوي الوسوم على مفتاح وقيمة، مع العلم أن القيمة اختيارية. بعد تطبيقها، يمكن الاستعلام عن المحتوى باستخدام اسم الوسم وقيمته.',
|
||||
'tag_name' => 'اسم العلامة',
|
||||
'tag_value' => 'قيمة الوسم (اختياري)',
|
||||
'tags_explain' => "إضافة الوسوم تساعد بترتيب وتقسيم المحتوى. \n من الممكن وضع قيمة لكل وسم لترتيب أفضل وأدق.",
|
||||
'tags_add' => 'إضافة وسم آخر',
|
||||
'tags_remove' => 'إزالة هذه العلامة',
|
||||
'tags_usages' => 'Total tag usages',
|
||||
'tags_assigned_pages' => 'Assigned to Pages',
|
||||
'tags_assigned_chapters' => 'Assigned to Chapters',
|
||||
'tags_assigned_books' => 'Assigned to Books',
|
||||
'tags_assigned_shelves' => 'Assigned to Shelves',
|
||||
'tags_x_unique_values' => ':count unique values',
|
||||
'tags_all_values' => 'All values',
|
||||
'tags_view_tags' => 'View Tags',
|
||||
'tags_view_existing_tags' => 'View existing tags',
|
||||
'tags_list_empty_hint' => 'Tags can be assigned via the page editor sidebar or while editing the details of a book, chapter or shelf.',
|
||||
'tags_usages' => 'إجمالي استخدامات العلامة',
|
||||
'tags_assigned_pages' => 'مُخصصة للصفحات',
|
||||
'tags_assigned_chapters' => 'مُخصصة للفصول',
|
||||
'tags_assigned_books' => 'مُخصص للكتب',
|
||||
'tags_assigned_shelves' => 'مُخصصة للأرفف',
|
||||
'tags_x_unique_values' => 'قيم الفريدة :count',
|
||||
'tags_all_values' => 'جميع القيم',
|
||||
'tags_view_tags' => 'عرض العلامات',
|
||||
'tags_view_existing_tags' => 'عرض العلامات الموجودة',
|
||||
'tags_list_empty_hint' => 'يمكن تعيين العلامات بواسطة الشريط الجانبي لمحرر الصفحة أو خلال تحرير تفاصيل الكتاب أو الفصل أو الرف.',
|
||||
'attachments' => 'المرفقات',
|
||||
'attachments_explain' => 'ارفع بعض الملفات أو أرفق بعض الروابط لعرضها بصفحتك. ستكون الملفات والروابط معروضة في الشريط الجانبي للصفحة.',
|
||||
'attachments_explain_instant_save' => 'سيتم حفظ التغييرات هنا آنيا.',
|
||||
'attachments_upload' => 'رفع ملف',
|
||||
'attachments_link' => 'إرفاق رابط',
|
||||
'attachments_upload_drop' => 'Alternatively you can drag and drop a file here to upload it as an attachment.',
|
||||
'attachments_upload_drop' => 'وبدلاً من ذلك، يمكنك سحب المِلَفّ وإفلاته هنا لتحميله كمرفق.',
|
||||
'attachments_set_link' => 'تحديد الرابط',
|
||||
'attachments_delete' => 'هل أنت متأكد من أنك تريد حذف هذا المرفق؟',
|
||||
'attachments_dropzone' => 'Drop files here to upload',
|
||||
'attachments_dropzone' => 'قم بإسقاط الملفات هنا للتحميل',
|
||||
'attachments_no_files' => 'لم تُرفع أي ملفات',
|
||||
'attachments_explain_link' => 'بالإمكان إرفاق رابط في حال عدم تفضيل رفع ملف. قد يكون الرابط لصفحة أخرى أو لملف في أحد خدمات التخزين السحابي.',
|
||||
'attachments_link_name' => 'اسم الرابط',
|
||||
@@ -392,19 +393,28 @@ return [
|
||||
'comment' => 'تعليق',
|
||||
'comments' => 'تعليقات',
|
||||
'comment_add' => 'إضافة تعليق',
|
||||
'comment_none' => 'لا توجد تعليقات لعرضها',
|
||||
'comment_placeholder' => 'ضع تعليقاً هنا',
|
||||
'comment_count' => '{0} لا توجد تعليقات|{1} تعليق واحد|{2} تعليقان[3,*] :count تعليقات',
|
||||
'comment_thread_count' => ':count تعليقات| :count تعليقات',
|
||||
'comment_archived_count' => ':count مؤرشف',
|
||||
'comment_archived_threads' => 'المواضيع المؤرشفة',
|
||||
'comment_save' => 'حفظ التعليق',
|
||||
'comment_new' => 'تعليق جديد',
|
||||
'comment_created' => 'تم التعليق :createDiff',
|
||||
'comment_updated' => 'تم التحديث :updateDiff بواسطة :username',
|
||||
'comment_updated_indicator' => 'Updated',
|
||||
'comment_updated_indicator' => 'تم التحديث',
|
||||
'comment_deleted_success' => 'تم حذف التعليق',
|
||||
'comment_created_success' => 'تمت إضافة التعليق',
|
||||
'comment_updated_success' => 'تم تحديث التعليق',
|
||||
'comment_archive_success' => 'تم أرشفة التعليق',
|
||||
'comment_unarchive_success' => 'تعليق غير مؤرشف',
|
||||
'comment_view' => 'عرض التعليق',
|
||||
'comment_jump_to_thread' => 'انتقل إلى الموضوع',
|
||||
'comment_delete_confirm' => 'تأكيد حذف التعليق؟',
|
||||
'comment_in_reply_to' => 'رداً على :commentId',
|
||||
'comment_editor_explain' => 'Here are the comments that have been left on this page. Comments can be added & managed when viewing the saved page.',
|
||||
'comment_reference' => 'المرجع',
|
||||
'comment_reference_outdated' => '(قديمة)',
|
||||
'comment_editor_explain' => 'هذه هي التعليقات المُضافة على هذه الصفحة. يُمكنك إضافة التعليقات وإدارتها عند عرض الصفحة المحفوظة.',
|
||||
|
||||
// Revision
|
||||
'revision_delete_confirm' => 'هل أنت متأكد من أنك تريد حذف هذه المراجعة؟',
|
||||
@@ -412,51 +422,51 @@ return [
|
||||
'revision_cannot_delete_latest' => 'لايمكن حذف آخر مراجعة.',
|
||||
|
||||
// Copy view
|
||||
'copy_consider' => 'Please consider the below when copying content.',
|
||||
'copy_consider_permissions' => 'Custom permission settings will not be copied.',
|
||||
'copy_consider_owner' => 'You will become the owner of all copied content.',
|
||||
'copy_consider_images' => 'Page image files will not be duplicated & the original images will retain their relation to the page they were originally uploaded to.',
|
||||
'copy_consider_attachments' => 'Page attachments will not be copied.',
|
||||
'copy_consider_access' => 'A change of location, owner or permissions may result in this content being accessible to those previously without access.',
|
||||
'copy_consider' => 'يرجى مراعاة ما يلي عند نسخ المحتوى.',
|
||||
'copy_consider_permissions' => 'لن يتم نسخ إعدادات الصلاحيات المخصصة.',
|
||||
'copy_consider_owner' => 'سوف تصبح مالكًا لجميع المحتوى المنسوخ.',
|
||||
'copy_consider_images' => 'لن يتم تكرار ملفات صور الصفحة وستحتفظ الصور الأصلية بعلاقتها بالصفحة التي تم تحميلها إليها في الأصل.',
|
||||
'copy_consider_attachments' => 'لن يتم نسخ مرفقات الصفحة.',
|
||||
'copy_consider_access' => 'قد يؤدي تغيير الموقع أو المالك أو الصلاحيات إلى إمكانية وصول الأشخاص الذين لم يتمكنوا من الوصول إلى هذا المحتوى سابقًا.',
|
||||
|
||||
// Conversions
|
||||
'convert_to_shelf' => 'Convert to Shelf',
|
||||
'convert_to_shelf_contents_desc' => 'You can convert this book to a new shelf with the same contents. Chapters contained within this book will be converted to new books. If this book contains any pages, that are not in a chapter, this book will be renamed and contain such pages, and this book will become part of the new shelf.',
|
||||
'convert_to_shelf_permissions_desc' => 'Any permissions set on this book will be copied to the new shelf and to all new child books that don\'t have their own permissions enforced. Note that permissions on shelves do not auto-cascade to content within, as they do for books.',
|
||||
'convert_book' => 'Convert Book',
|
||||
'convert_book_confirm' => 'Are you sure you want to convert this book?',
|
||||
'convert_undo_warning' => 'This cannot be as easily undone.',
|
||||
'convert_to_book' => 'Convert to Book',
|
||||
'convert_to_book_desc' => 'You can convert this chapter to a new book with the same contents. Any permissions set on this chapter will be copied to the new book but any inherited permissions, from the parent book, will not be copied which could lead to a change of access control.',
|
||||
'convert_chapter' => 'Convert Chapter',
|
||||
'convert_chapter_confirm' => 'Are you sure you want to convert this chapter?',
|
||||
'convert_to_shelf' => 'تحويل إلى رف',
|
||||
'convert_to_shelf_contents_desc' => 'يمكنك تحويل هذا الكتاب إلى رف جديد بنفس المحتويات. سيتم تحويل الفصول الموجودة فيه إلى كتب جديدة. إذا احتوى هذا الكتاب على أي صفحات غير موجودة في أي فصل، فسيتم إعادة تسمية الكتاب وإضافة هذه الصفحات إليه، وسيصبح جزءًا من الرف الجديد.',
|
||||
'convert_to_shelf_permissions_desc' => 'سيتم نسخ أي صلاحيات مُحددة لهذا الكتاب إلى الرف الجديد وإلى جميع الكتب الفرعية الجديدة التي لم تُطبّق عليها صلاحيات خاصة بها. يُرجى العلم بأن الصلاحيات على الرفوف لا تنتقل تلقائيًا إلى المحتوى داخلها، كما هو الحال مع الكتب.',
|
||||
'convert_book' => 'تحويل الكتاب',
|
||||
'convert_book_confirm' => 'هل أنت متيقِّن أنك تريد تحويل هذا الكتاب؟',
|
||||
'convert_undo_warning' => 'لا يمكن التراجع عن هذا الأمر بسهولة.',
|
||||
'convert_to_book' => 'تحويله إلى كتاب',
|
||||
'convert_to_book_desc' => 'يمكنك تحويل هذا الفصل إلى كتاب جديد بنفس المحتوى. سيتم نسخ أي صلاحيات مُعيّنة لهذا الفصل إلى الكتاب الجديد، ولكن لن يتم نسخ أي صلاحيات موروثة من الكتاب الأصلي، مما قد يؤدي إلى تغيير في التحكم في الوصول.',
|
||||
'convert_chapter' => 'تحويل الفصل',
|
||||
'convert_chapter_confirm' => 'هل أنت متيقِّن أنك تريد تحويل هذا الفصل؟',
|
||||
|
||||
// References
|
||||
'references' => 'References',
|
||||
'references_none' => 'There are no tracked references to this item.',
|
||||
'references_to_desc' => 'Listed below is all the known content in the system that links to this item.',
|
||||
'references' => 'مراجع',
|
||||
'references_none' => 'لا توجد مراجع متعقبة لهذا العنصر.',
|
||||
'references_to_desc' => 'تجد أدناه كل المحتوى المعروف في النظام المرتبط بهذا العنصر.',
|
||||
|
||||
// Watch Options
|
||||
'watch' => 'Watch',
|
||||
'watch_title_default' => 'Default Preferences',
|
||||
'watch_desc_default' => 'Revert watching to just your default notification preferences.',
|
||||
'watch_title_ignore' => 'Ignore',
|
||||
'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.',
|
||||
'watch_title_new' => 'New Pages',
|
||||
'watch_desc_new' => 'Notify when any new page is created within this item.',
|
||||
'watch_title_updates' => 'All Page Updates',
|
||||
'watch_desc_updates' => 'Notify upon all new pages and page changes.',
|
||||
'watch_desc_updates_page' => 'Notify upon all page changes.',
|
||||
'watch_title_comments' => 'All Page Updates & Comments',
|
||||
'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.',
|
||||
'watch_desc_comments_page' => 'Notify upon page changes and new comments.',
|
||||
'watch_change_default' => 'Change default notification preferences',
|
||||
'watch_detail_ignore' => 'Ignoring notifications',
|
||||
'watch_detail_new' => 'Watching for new pages',
|
||||
'watch_detail_updates' => 'Watching new pages and updates',
|
||||
'watch_detail_comments' => 'Watching new pages, updates & comments',
|
||||
'watch_detail_parent_book' => 'Watching via parent book',
|
||||
'watch_detail_parent_book_ignore' => 'Ignoring via parent book',
|
||||
'watch_detail_parent_chapter' => 'Watching via parent chapter',
|
||||
'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter',
|
||||
'watch' => 'شاهد',
|
||||
'watch_title_default' => 'التفضيلات الافتراضية',
|
||||
'watch_desc_default' => 'استعادة المشاهدة إلى تفضيلات الإشعارات الافتراضية فقط.',
|
||||
'watch_title_ignore' => 'تجاهل',
|
||||
'watch_desc_ignore' => 'تجاهل كافة الإشعارات، بما في ذلك تلك الواردة من تفضيلات مستوى المستخدم.',
|
||||
'watch_title_new' => 'صفحات جديدة',
|
||||
'watch_desc_new' => 'إعلام عند إنشاء أي صفحة جديدة ضمن هذا العنصر.',
|
||||
'watch_title_updates' => 'جميع تحديثات الصفحة',
|
||||
'watch_desc_updates' => 'إشعار بجميع الصفحات الجديدة والتغييرات في الصفحات.',
|
||||
'watch_desc_updates_page' => 'إشعار عند حدوث أي تغييرات في الصفحة.',
|
||||
'watch_title_comments' => 'جميع تحديثات الصفحة والتعليقات',
|
||||
'watch_desc_comments' => 'إشعار بجميع الصفحات الجديدة، وتغييرات الصفحات والتعليقات الجديدة.',
|
||||
'watch_desc_comments_page' => 'إشعار عند حدوث تغييرات في الصفحة أو تعليقات جديدة.',
|
||||
'watch_change_default' => 'تغيير تفضيلات الإشعارات الافتراضية',
|
||||
'watch_detail_ignore' => 'تجاهل الإشعارات',
|
||||
'watch_detail_new' => 'ترقب الصفحات الجديدة',
|
||||
'watch_detail_updates' => 'مشاهدة الصفحات الجديدة والتحديثات',
|
||||
'watch_detail_comments' => 'مشاهدة الصفحات الجديدة والتحديثات والتعليقات',
|
||||
'watch_detail_parent_book' => 'المشاهدة عبر الكتاب الرئيس',
|
||||
'watch_detail_parent_book_ignore' => 'التجاهل عبر الكتاب الرئيس',
|
||||
'watch_detail_parent_chapter' => 'المشاهدة عبر الفصل الرئيس',
|
||||
'watch_detail_parent_chapter_ignore' => 'التجاهل عبر الفصل الرئيس',
|
||||
];
|
||||
|
||||
@@ -10,13 +10,13 @@ return [
|
||||
|
||||
// Auth
|
||||
'error_user_exists_different_creds' => 'يوجد مستخدم ببيانات مختلفة مسجل بالنظام للبريد الإلكتروني :email.',
|
||||
'auth_pre_register_theme_prevention' => 'User account could not be registered for the provided details',
|
||||
'auth_pre_register_theme_prevention' => 'لم يتمكن حساب المستخدم من التسجيل للحصول على التفاصيل المقدمة',
|
||||
'email_already_confirmed' => 'تم تأكيد البريد الإلكتروني من قبل, الرجاء محاولة تسجيل الدخول.',
|
||||
'email_confirmation_invalid' => 'رابط التأكيد غير صحيح أو قد تم استخدامه من قبل, الرجاء محاولة التسجيل من جديد.',
|
||||
'email_confirmation_expired' => 'صلاحية رابط التأكيد انتهت, تم إرسال رسالة تأكيد جديدة لعنوان البريد الإلكتروني.',
|
||||
'email_confirmation_awaiting' => 'عنوان البريد الإلكتروني للحساب قيد الاستخدام يحتاج إلى تأكيد',
|
||||
'ldap_fail_anonymous' => 'فشل الوصول إلى LDAP باستخدام الربط المجهول',
|
||||
'ldap_fail_authed' => 'فشل الوصول إلى LDAP باستخدام dn و password المعطاة',
|
||||
'ldap_fail_authed' => 'فشل الوصول إلى LDAP باستخدام dn و كلمة السر المعطاة',
|
||||
'ldap_extension_not_installed' => 'لم يتم تثبيت إضافة LDAP PHP',
|
||||
'ldap_cannot_connect' => 'لا يمكن الاتصال بخادم ldap, فشل الاتصال المبدئي',
|
||||
'saml_already_logged_in' => 'تم تسجيل الدخول بالفعل',
|
||||
@@ -37,7 +37,7 @@ return [
|
||||
'social_driver_not_found' => 'لم يتم العثور على السوشيال درايفر "Social driver"',
|
||||
'social_driver_not_configured' => 'لم يتم تهيئة إعدادات حسابك الاجتماعي بشكل صحيح.',
|
||||
'invite_token_expired' => 'انتهت صلاحية رابط هذه الدعوة. يمكنك بدلاً من ذلك محاولة إعادة تعيين كلمة مرور حسابك.',
|
||||
'login_user_not_found' => 'A user for this action could not be found.',
|
||||
'login_user_not_found' => 'لم يتم العثور على مستخدم لهذا الإجراء.',
|
||||
|
||||
// System
|
||||
'path_not_writable' => 'لا يمكن الرفع إلى مسار :filePath. الرجاء التأكد من قابلية الكتابة إلى الخادم.',
|
||||
@@ -78,7 +78,7 @@ return [
|
||||
// Users
|
||||
'users_cannot_delete_only_admin' => 'لا يمكن حذف المشرف الوحيد',
|
||||
'users_cannot_delete_guest' => 'لا يمكن حذف المستخدم الضيف',
|
||||
'users_could_not_send_invite' => 'Could not create user since invite email failed to send',
|
||||
'users_could_not_send_invite' => 'لم يتم إنشاء المستخدم بسبب فشل إرسال بريد الدعوة',
|
||||
|
||||
// Roles
|
||||
'role_cannot_be_edited' => 'لا يمكن تعديل هذا الدور',
|
||||
@@ -106,16 +106,16 @@ return [
|
||||
'back_soon' => 'سيعود للعمل قريباً.',
|
||||
|
||||
// Import
|
||||
'import_zip_cant_read' => 'Could not read ZIP file.',
|
||||
'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.',
|
||||
'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.',
|
||||
'import_validation_failed' => 'Import ZIP failed to validate with errors:',
|
||||
'import_zip_failed_notification' => 'Failed to import ZIP file.',
|
||||
'import_perms_books' => 'You are lacking the required permissions to create books.',
|
||||
'import_perms_chapters' => 'You are lacking the required permissions to create chapters.',
|
||||
'import_perms_pages' => 'You are lacking the required permissions to create pages.',
|
||||
'import_perms_images' => 'You are lacking the required permissions to create images.',
|
||||
'import_perms_attachments' => 'You are lacking the required permission to create attachments.',
|
||||
'import_zip_cant_read' => 'لم أتمكن من قراءة المِلَفّ المضغوط -ZIP-.',
|
||||
'import_zip_cant_decode_data' => 'لم نتمكن من العثور على محتوى المِلَفّ المضغوط data.json وفك تشفيره.',
|
||||
'import_zip_no_data' => 'لا تتضمن بيانات المِلَفّ المضغوط أي محتوى متوقع للكتاب أو الفصل أو الصفحة.',
|
||||
'import_validation_failed' => 'فشل التحقق من صحة استيراد المِلَفّ المضغوط بسبب الأخطاء التالية:',
|
||||
'import_zip_failed_notification' => 'فشل استيراد المِلَفّ المضغوط.',
|
||||
'import_perms_books' => 'أنت تفتقر إلى الصلاحيات المطلوبة لإنشاء الكتب.',
|
||||
'import_perms_chapters' => 'أنت تفتقر إلى الصلاحيات المطلوبة لإنشاء الفصول.',
|
||||
'import_perms_pages' => 'أنت تفتقر إلى الصلاحيات المطلوبة لإنشاء الصفحات.',
|
||||
'import_perms_images' => 'أنت تفتقر إلى الصلاحيات المطلوبة لإنشاء الصور.',
|
||||
'import_perms_attachments' => 'أنت تفتقر إلى الصَّلاحِيَة المطلوب لإنشاء المرفقات.',
|
||||
|
||||
// API errors
|
||||
'api_no_authorization_found' => 'لم يتم العثور على رمز ترخيص مميز في الطلب',
|
||||
|
||||
@@ -4,24 +4,24 @@
|
||||
*/
|
||||
return [
|
||||
|
||||
'new_comment_subject' => 'New comment on page: :pageName',
|
||||
'new_comment_intro' => 'A user has commented on a page in :appName:',
|
||||
'new_page_subject' => 'New page: :pageName',
|
||||
'new_page_intro' => 'A new page has been created in :appName:',
|
||||
'updated_page_subject' => 'Updated page: :pageName',
|
||||
'updated_page_intro' => 'A page has been updated in :appName:',
|
||||
'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.',
|
||||
'new_comment_subject' => 'تعليق جديد على الصفحة: :pageName',
|
||||
'new_comment_intro' => 'قام أحد المستخدمين بالتعليق على صفحة في :appName:',
|
||||
'new_page_subject' => 'صفحة جديدة: :pageName',
|
||||
'new_page_intro' => 'تم إنشاء صفحة جديدة في :appName:',
|
||||
'updated_page_subject' => 'تم تحديث الصفحة: :pageName',
|
||||
'updated_page_intro' => 'تم تحديث الصفحة في :appName:',
|
||||
'updated_page_debounce' => 'لمنع تلقي عدد كبير من الإشعارات، لن يتم إرسال إشعارات إليك لفترة من الوقت لإجراء المزيد من التعديلات على هذه الصفحة بواسطة نفس المحرر.',
|
||||
|
||||
'detail_page_name' => 'Page Name:',
|
||||
'detail_page_path' => 'Page Path:',
|
||||
'detail_commenter' => 'Commenter:',
|
||||
'detail_comment' => 'Comment:',
|
||||
'detail_created_by' => 'Created By:',
|
||||
'detail_updated_by' => 'Updated By:',
|
||||
'detail_page_name' => 'اسم الصفحة:',
|
||||
'detail_page_path' => 'مسار الصفحة:',
|
||||
'detail_commenter' => 'المُعَلِق:',
|
||||
'detail_comment' => 'التعليق:',
|
||||
'detail_created_by' => 'أنشئ من طرف:',
|
||||
'detail_updated_by' => 'تم التحديث بواسطة:',
|
||||
|
||||
'action_view_comment' => 'View Comment',
|
||||
'action_view_page' => 'View Page',
|
||||
'action_view_comment' => 'عرض التعليق',
|
||||
'action_view_page' => 'عرض الصفحة',
|
||||
|
||||
'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.',
|
||||
'footer_reason_link' => 'your notification preferences',
|
||||
'footer_reason' => 'لقد تم إرسال هذا الإشعار إليك لأن :link يغطي هذا النوع من النشاط لهذا العنصر.',
|
||||
'footer_reason_link' => 'إعدادات الإشعارات الخاصة بك',
|
||||
];
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
*/
|
||||
return [
|
||||
|
||||
'password' => 'يجب أن تتكون كلمة المرور من ستة أحرف على الأقل وأن تطابق التأكيد.',
|
||||
'password' => 'يجب أن تتكون كلمة السر من ستة أحرف على الأقل وأن تطابق التأكيد.',
|
||||
'user' => "لم يتم العثور على مستخدم بعنوان البريد الإلكتروني المعطى.",
|
||||
'token' => 'رمز إعادة تعيين كلمة المرور غير صالح لعنوان هذا البريد الإلكتروني.',
|
||||
'sent' => 'تم إرسال رابط تجديد كلمة المرور إلى بريدكم الإلكتروني!',
|
||||
'reset' => 'تم تجديد كلمة المرور الخاصة بكم!',
|
||||
'token' => 'رمز إعادة تعيين كلمة السر غير صالح لعنوان هذا البريد الإلكتروني.',
|
||||
'sent' => 'تم إرسال رابط تجديد كلمة السر إلى بريدكم الإلكتروني!',
|
||||
'reset' => 'تم تجديد كلمة السر الخاصة بكم!',
|
||||
|
||||
];
|
||||
|
||||
@@ -5,47 +5,47 @@
|
||||
*/
|
||||
|
||||
return [
|
||||
'my_account' => 'My Account',
|
||||
'my_account' => 'حسابي',
|
||||
|
||||
'shortcuts' => 'Shortcuts',
|
||||
'shortcuts_interface' => 'UI Shortcut Preferences',
|
||||
'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!',
|
||||
'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.',
|
||||
'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_overview_desc' => 'إدارة اختصارات لوحة المفاتيح التي يمكنك استخدامها للتنقل في واجهة مستخدم النظام.',
|
||||
|
||||
'notifications' => 'Notification Preferences',
|
||||
'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.',
|
||||
'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own',
|
||||
'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own',
|
||||
'notifications_opt_comment_replies' => 'Notify upon replies to my comments',
|
||||
'notifications_save' => 'Save Preferences',
|
||||
'notifications_update_success' => 'Notification preferences have been updated!',
|
||||
'notifications_watched' => 'Watched & Ignored Items',
|
||||
'notifications_watched_desc' => 'Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.',
|
||||
'notifications' => 'إعدادات الإشعارات',
|
||||
'notifications_desc' => 'التحكم في إشعارات البريد الإلكتروني الذي تتلقاها عند إجراء نشاط معين داخل النظام.',
|
||||
'notifications_opt_own_page_changes' => 'إشعاري عند حدوث تغييرات في الصفحات التي أملكها',
|
||||
'notifications_opt_own_page_comments' => 'إشعاري بشأن التعليقات على الصفحات التي أملكها',
|
||||
'notifications_opt_comment_replies' => 'إشعاري عند الردود على تعليقاتي',
|
||||
'notifications_save' => 'حفظ اﻹعدادات',
|
||||
'notifications_update_success' => 'تم تحديث إعدادات الإشعارات!',
|
||||
'notifications_watched' => 'العناصر التي تمت مشاهدتها وتجاهلها',
|
||||
'notifications_watched_desc' => 'فيما يلي العناصر التي طُبِّقت عليها إعدادات ساعة مخصصة. لتحديث إعداداتك، استعرض العنصر ثم ابحث عن خيارات الساعة في الشريط الجانبي.',
|
||||
|
||||
'auth' => 'Access & Security',
|
||||
'auth_change_password' => 'Change Password',
|
||||
'auth_change_password_desc' => 'Change the password you use to log-in to the application. This must be at least 8 characters long.',
|
||||
'auth_change_password_success' => 'Password has been updated!',
|
||||
'auth' => 'الوصول و الأمان',
|
||||
'auth_change_password' => 'تغيير كلمة السر',
|
||||
'auth_change_password_desc' => 'غيّر كلمة السر التي تستخدمها لتسجيل الدخول إلى التطبيق. يجب ألا تقل عن 8 أحرف.',
|
||||
'auth_change_password_success' => 'تم تحديث كلمة السر!',
|
||||
|
||||
'profile' => 'Profile Details',
|
||||
'profile_desc' => 'Manage the details of your account which represents you to other users, in addition to details that are used for communication and system personalisation.',
|
||||
'profile_view_public' => 'View Public Profile',
|
||||
'profile_name_desc' => 'Configure your display name which will be visible to other users in the system through the activity you perform, and content you own.',
|
||||
'profile_email_desc' => 'This email will be used for notifications and, depending on active system authentication, system access.',
|
||||
'profile_email_no_permission' => 'Unfortunately you don\'t have permission to change your email address. If you want to change this, you\'d need to ask an administrator to change this for you.',
|
||||
'profile_avatar_desc' => 'Select an image which will be used to represent yourself to others in the system. Ideally this image should be square and about 256px in width and height.',
|
||||
'profile_admin_options' => 'Administrator Options',
|
||||
'profile_admin_options_desc' => 'Additional administrator-level options, like those to manage role assignments, can be found for your user account in the "Settings > Users" area of the application.',
|
||||
'profile' => 'تفاصيل المِلَفّ الشخصي',
|
||||
'profile_desc' => 'إدارة تفاصيل حسابك الذي يمثلك أمام المستخدمين الآخرين، بالإضافة إلى التفاصيل المستخدمة للتواصل وتخصيص النظام.',
|
||||
'profile_view_public' => 'عرض المِلَفّ الشخصي العام',
|
||||
'profile_name_desc' => 'إعداد اسم العرض الخاص بك الذي سيكون مرئيًا للمستخدمين الآخرين في النظام من خلال النشاط الذي تقوم به والمحتوى الذي تملكه.',
|
||||
'profile_email_desc' => 'سيتم استخدام هذا البريد الإلكتروني للإشعارات، وبناءً على مصادقة النظام النشط، سيتم استخدام الوصول إلى النظام.',
|
||||
'profile_email_no_permission' => 'للأسف، ليس لديك إذن لتغيير عنوان بريدك الإلكتروني. إذا كنت ترغب في تغييره، فعليك طلب ذلك من أحد المسؤولين.',
|
||||
'profile_avatar_desc' => 'اختر صورةً تُمثّلك أمام الآخرين في النظام. يُفضّل أن تكون الصورة مربعةً، وعرضها وارتفاعها حوالي ٢٥٦ بكسل.',
|
||||
'profile_admin_options' => 'خيارات المسؤول',
|
||||
'profile_admin_options_desc' => 'يمكنك العثور على خيارات إضافية على مستوى المسؤول، مثل تلك الخاصة بإدارة تعيينات الأدوار، لحساب المستخدم الخاص بك في منطقة "الإعدادات > المستخدمون" في التطبيق.',
|
||||
|
||||
'delete_account' => 'Delete Account',
|
||||
'delete_my_account' => 'Delete My Account',
|
||||
'delete_my_account_desc' => 'This will fully delete your user account from the system. You will not be able to recover this account or revert this action. Content you\'ve created, such as created pages and uploaded images, will remain.',
|
||||
'delete_my_account_warning' => 'Are you sure you want to delete your account?',
|
||||
'delete_account' => 'حذف الحساب',
|
||||
'delete_my_account' => 'حذف حسابي',
|
||||
'delete_my_account_desc' => 'سيؤدي هذا إلى حذف حساب المستخدم الخاص بك بالكامل من النظام. لن تتمكن من استعادة هذا الحساب أو التراجع عن هذا الإجراء. سيبقى المحتوى الذي أنشأته، مثل الصفحات التي أنشأتها والصور التي رفعتها، كما هي.',
|
||||
'delete_my_account_warning' => 'هل أنت متأكد أنك تريد حذف حسابك؟',
|
||||
];
|
||||
|
||||
@@ -48,12 +48,12 @@ return [
|
||||
'app_disable_comments_desc' => 'تعطيل التعليقات على جميع الصفحات داخل التطبيق. التعليقات الموجودة من الأصل لن تكون ظاهرة.',
|
||||
|
||||
// Color settings
|
||||
'color_scheme' => 'Application Color Scheme',
|
||||
'color_scheme_desc' => 'Set the colors to use in the application user interface. Colors can be configured separately for dark and light modes to best fit the theme and ensure legibility.',
|
||||
'ui_colors_desc' => 'Set the application primary color and default link color. The primary color is mainly used for the header banner, buttons and interface decorations. The default link color is used for text-based links and actions, both within written content and in the application interface.',
|
||||
'app_color' => 'Primary Color',
|
||||
'link_color' => 'Default Link Color',
|
||||
'content_colors_desc' => 'Set colors for all elements in the page organisation hierarchy. Choosing colors with a similar brightness to the default colors is recommended for readability.',
|
||||
'color_scheme' => 'مخطط ألوان التطبيق',
|
||||
'color_scheme_desc' => 'حدّد الألوان المستخدمة في واجهة مستخدم التطبيق. يمكن ضبط الألوان بشكل منفصل للوضعين الداكن والفاتح لتناسب المظهر بشكل أفضل ولضمان وضوح النص.',
|
||||
'ui_colors_desc' => 'عيّن اللون الأساسي للتطبيق ولون الرابط الافتراضي. يُستخدم اللون الأساسي بشكل رئيس في شعار الصفحة الرئيسة والأزرار وزخارف الواجهة. أما اللون الافتراضي للرابط، فيُستخدم للروابط والإجراءات النصية، سواءً داخل المحتوى المكتوب أو في واجهة التطبيق.',
|
||||
'app_color' => 'اللون الأساسي',
|
||||
'link_color' => 'لون الرابط الافتراضي',
|
||||
'content_colors_desc' => 'حدّد ألوان جميع عناصر هيكل تنظيم الصفحة. يُنصح باختيار ألوان بنفس سطوع الألوان الافتراضية لسهولة القراءة.',
|
||||
'bookshelf_color' => 'لون الرف',
|
||||
'book_color' => 'لون الكتاب',
|
||||
'chapter_color' => 'لون الفصل',
|
||||
@@ -75,40 +75,40 @@ return [
|
||||
'reg_confirm_restrict_domain_placeholder' => 'لم يتم اختيار أي قيود',
|
||||
|
||||
// Sorting Settings
|
||||
'sorting' => 'Sorting',
|
||||
'sorting_book_default' => 'Default Book Sort',
|
||||
'sorting_book_default_desc' => 'Select the default sort rule to apply to new books. This won\'t affect existing books, and can be overridden per-book.',
|
||||
'sorting_rules' => 'Sort Rules',
|
||||
'sorting_rules_desc' => 'These are predefined sorting operations which can be applied to content in the system.',
|
||||
'sort_rule_assigned_to_x_books' => 'Assigned to :count Book|Assigned to :count Books',
|
||||
'sort_rule_create' => 'Create Sort Rule',
|
||||
'sort_rule_edit' => 'Edit Sort Rule',
|
||||
'sort_rule_delete' => 'Delete Sort Rule',
|
||||
'sort_rule_delete_desc' => 'Remove this sort rule from the system. Books using this sort will revert to manual sorting.',
|
||||
'sort_rule_delete_warn_books' => 'This sort rule is currently used on :count book(s). Are you sure you want to delete this?',
|
||||
'sort_rule_delete_warn_default' => 'This sort rule is currently used as the default for books. Are you sure you want to delete this?',
|
||||
'sort_rule_details' => 'Sort Rule Details',
|
||||
'sort_rule_details_desc' => 'Set a name for this sort rule, which will appear in lists when users are selecting a sort.',
|
||||
'sort_rule_operations' => 'Sort Operations',
|
||||
'sort_rule_operations_desc' => 'Configure the sort actions to be performed by moving them from the list of available operations. Upon use, the operations will be applied in order, from top to bottom. Any changes made here will be applied to all assigned books upon save.',
|
||||
'sort_rule_available_operations' => 'Available Operations',
|
||||
'sort_rule_available_operations_empty' => 'No operations remaining',
|
||||
'sort_rule_configured_operations' => 'Configured Operations',
|
||||
'sort_rule_configured_operations_empty' => 'Drag/add operations from the "Available Operations" list',
|
||||
'sort_rule_op_asc' => '(Asc)',
|
||||
'sort_rule_op_desc' => '(Desc)',
|
||||
'sort_rule_op_name' => 'Name - Alphabetical',
|
||||
'sort_rule_op_name_numeric' => 'Name - Numeric',
|
||||
'sort_rule_op_created_date' => 'Created Date',
|
||||
'sort_rule_op_updated_date' => 'Updated Date',
|
||||
'sort_rule_op_chapters_first' => 'Chapters First',
|
||||
'sort_rule_op_chapters_last' => 'Chapters Last',
|
||||
'sorting' => 'طريقة الترتيب',
|
||||
'sorting_book_default' => 'ترتيب الكتاب الافتراضي',
|
||||
'sorting_book_default_desc' => 'حدد قاعدة الترتيب الافتراضية لتطبيقها على الكتب الجديدة. لن يؤثر هذا على الكتب الحالية، ويمكن تجاوزه لكل كتاب على حدة.',
|
||||
'sorting_rules' => 'قواعد الترتيب',
|
||||
'sorting_rules_desc' => 'هذه هي عمليات الترتيب المحددة مسبقًا الذي يمكن تطبيقها على المحتوى الموجود في النظام.',
|
||||
'sort_rule_assigned_to_x_books' => 'مُعيَّن إلى :count كتاب|مُعيَّن إلى :count كتاب',
|
||||
'sort_rule_create' => 'إنشاء قاعدة الترتيب',
|
||||
'sort_rule_edit' => 'تعديل قاعدة الترتيب',
|
||||
'sort_rule_delete' => 'حذف قاعدة الترتيب',
|
||||
'sort_rule_delete_desc' => 'أزل قاعدة الترتيب هذه من النظام. الكتب التي تستخدم هذا الفرز ستعود إلى الفرز اليدوي.',
|
||||
'sort_rule_delete_warn_books' => 'تُستخدم قاعدة الترتيب هذه حاليًا على :count كتاب/كتب. متيقن من رغبتك في حذف هذا؟',
|
||||
'sort_rule_delete_warn_default' => 'تُستخدم قاعدة الترتيب هذه حاليًا كإعداد افتراضي للكتب. متيقن من رغبتك في حذفها؟',
|
||||
'sort_rule_details' => 'تفاصيل قاعدة الترتيب',
|
||||
'sort_rule_details_desc' => 'تعيين اسم لقاعدة الترتيب هذه، التي ستظهر في القوائم عندما يقوم المستخدمون باختيار نوع ما.',
|
||||
'sort_rule_operations' => 'عمليات الترتيب',
|
||||
'sort_rule_operations_desc' => 'جهّز إجراءات الترتيب المطلوب تنفيذها بنقلها من قائمة العمليات المتاحة. عند الاستخدام، سيتم تطبيق العمليات بالترتيب من الأعلى إلى الأسفل. أي تغييرات تُجرى هنا ستُطبّق على جميع الكتب المُخصّصة عند الحفظ.',
|
||||
'sort_rule_available_operations' => 'العمليات المتاحة',
|
||||
'sort_rule_available_operations_empty' => 'لا توجد عمليات متبقية',
|
||||
'sort_rule_configured_operations' => 'العمليات المُهيأة',
|
||||
'sort_rule_configured_operations_empty' => 'اسحب/أضف العمليات من قائمة "العمليات المتاحة"',
|
||||
'sort_rule_op_asc' => '(تصاعدي)',
|
||||
'sort_rule_op_desc' => '(تنازلي)',
|
||||
'sort_rule_op_name' => 'الاسم - أبجديًا',
|
||||
'sort_rule_op_name_numeric' => 'الاسم - رقمي',
|
||||
'sort_rule_op_created_date' => 'تاريخ الإنشاء',
|
||||
'sort_rule_op_updated_date' => 'تاريخ التحديث',
|
||||
'sort_rule_op_chapters_first' => 'الفصول الأولى',
|
||||
'sort_rule_op_chapters_last' => 'الفصول الأخيرة',
|
||||
|
||||
// Maintenance settings
|
||||
'maint' => 'الصيانة',
|
||||
'maint_image_cleanup' => 'تنظيف الصور',
|
||||
'maint_image_cleanup_desc' => 'مسح الصفحة ومراجعة المحتوى للتحقق من أي الصور والرسوم المستخدمة حاليًا وأي الصور زائدة عن الحاجة. تأكد من إنشاء قاعدة بيانات كاملة و نسخة احتياطية للصور قبل تشغيل هذا.',
|
||||
'maint_delete_images_only_in_revisions' => 'Also delete images that only exist in old page revisions',
|
||||
'maint_delete_images_only_in_revisions' => 'قم أيضًا بحذف الصور الموجودة فقط في مراجعات الصفحة القديمة',
|
||||
'maint_image_cleanup_run' => 'بدء التنظيف',
|
||||
'maint_image_cleanup_warning' => 'يوجد عدد :count من الصور المحتمل عدم استخدامها. تأكيد حذف الصور؟',
|
||||
'maint_image_cleanup_success' => 'تم إيجاد وحذف عدد :count من الصور المحتمل عدم استخدامها!',
|
||||
@@ -122,16 +122,16 @@ return [
|
||||
'maint_send_test_email_mail_text' => 'تهانينا! كما تلقيت إشعار هذا البريد الإلكتروني، يبدو أن إعدادات البريد الإلكتروني الخاص بك قد تم تكوينها بشكل صحيح.',
|
||||
'maint_recycle_bin_desc' => 'تُرسل الأرفف والكتب والفصول والصفحات المحذوفة إلى سلة المحذوفات حتى يمكن استعادتها أو حذفها نهائيًا. قد يتم إزالة العناصر الأقدم في سلة المحذوفات تلقائيًا بعد فترة اعتمادًا على تكوين النظام.',
|
||||
'maint_recycle_bin_open' => 'افتح سلة المحذوفات',
|
||||
'maint_regen_references' => 'Regenerate References',
|
||||
'maint_regen_references_desc' => 'This action will rebuild the cross-item reference index within the database. This is usually handled automatically but this action can be useful to index old content or content added via unofficial methods.',
|
||||
'maint_regen_references_success' => 'Reference index has been regenerated!',
|
||||
'maint_timeout_command_note' => 'Note: This action can take time to run, which can lead to timeout issues in some web environments. As an alternative, this action be performed using a terminal command.',
|
||||
'maint_regen_references' => 'إعادة إنشاء المراجع',
|
||||
'maint_regen_references_desc' => 'سيعيد هذا الإجراء بناء فِهْرِس المراجع بين العناصر داخل قاعدة البيانات. عادةً ما يتم ذلك تلقائيًا، ولكنه قد يكون مفيدًا لفهرسة المحتوى القديم أو المحتوى المُضاف بطرق غير رسمية.',
|
||||
'maint_regen_references_success' => 'لقد تم تجديد فِهْرِس المرجع!',
|
||||
'maint_timeout_command_note' => 'ملاحظة: قد يستغرق تنفيذ هذا الإجراء بعض الوقت، مما قد يؤدي إلى مشاكل في مهلة التنفيذ في بعض بيئات الويب. كبديل، يمكن تنفيذ هذا الإجراء باستخدام سطر الأوامر.',
|
||||
|
||||
// Recycle Bin
|
||||
'recycle_bin' => 'سلة المحذوفات',
|
||||
'recycle_bin_desc' => 'هنا يمكنك استعادة العناصر التي تم حذفها أو اختيار إزالتها نهائيا من النظام. هذه القائمة غير مصفاة خلافاً لقوائم الأنشطة المماثلة في النظام حيث يتم تطبيق عوامل تصفية الأذونات.',
|
||||
'recycle_bin_deleted_item' => 'عنصر محذوف',
|
||||
'recycle_bin_deleted_parent' => 'Parent',
|
||||
'recycle_bin_deleted_parent' => 'اﻷب',
|
||||
'recycle_bin_deleted_by' => 'حُذف بواسطة',
|
||||
'recycle_bin_deleted_at' => 'وقت الحذف',
|
||||
'recycle_bin_permanently_delete' => 'حُذف نهائيًا',
|
||||
@@ -139,12 +139,12 @@ return [
|
||||
'recycle_bin_contents_empty' => 'سلة المحذوفات فارغة حاليًا',
|
||||
'recycle_bin_empty' => 'إفراغ سلة المحذوفات',
|
||||
'recycle_bin_empty_confirm' => 'سيؤدي هذا إلى إتلاف جميع العناصر الموجودة في سلة المحذوفات بشكل دائم بما في ذلك المحتوى الموجود داخل كل عنصر. هل أنت متأكد من أنك تريد إفراغ سلة المحذوفات؟',
|
||||
'recycle_bin_destroy_confirm' => 'This action will permanently delete this item from the system, along with any child elements listed below, and you will not be able to restore this content. Are you sure you want to permanently delete this item?',
|
||||
'recycle_bin_destroy_confirm' => 'سيؤدي هذا الإجراء إلى حذف هذا العنصر نهائيًا من النظام، بالإضافة إلى أي عناصر فرعية مدرجة أدناه، ولن تتمكن من استعادة هذا المحتوى. هل أنت متيقِّن من رغبتك في حذف هذا العنصر نهائيًا؟',
|
||||
'recycle_bin_destroy_list' => 'العناصر المراد تدميرها',
|
||||
'recycle_bin_restore_list' => 'العناصر المراد استرجاعها',
|
||||
'recycle_bin_restore_confirm' => 'سيعيد هذا الإجراء العنصر المحذوف ، بما في ذلك أي عناصر فرعية ، إلى موقعه الأصلي. إذا تم حذف الموقع الأصلي منذ ذلك الحين ، وهو الآن في سلة المحذوفات ، فسيلزم أيضًا استعادة العنصر الأصلي.',
|
||||
'recycle_bin_restore_deleted_parent' => 'تم حذف أصل هذا العنصر أيضًا. سيبقى حذفه حتى يتم استعادة ذلك الأصل أيضًا.',
|
||||
'recycle_bin_restore_parent' => 'Restore Parent',
|
||||
'recycle_bin_restore_parent' => 'استعادة اﻷب',
|
||||
'recycle_bin_destroy_notification' => 'المحذوف: قُم بعد إجمالي العناصر من سلة المحذوفات.',
|
||||
'recycle_bin_restore_notification' => 'المرتجع: قُم بعد إجمالي العناصر من سلة المحذوفات.',
|
||||
|
||||
@@ -158,7 +158,7 @@ return [
|
||||
'audit_table_user' => 'المستخدم',
|
||||
'audit_table_event' => 'الحدث',
|
||||
'audit_table_related' => 'العنصر أو التفاصيل ذات الصلة',
|
||||
'audit_table_ip' => 'IP Address',
|
||||
'audit_table_ip' => 'عنوان عُرف اﻹنترنت -IP-',
|
||||
'audit_table_date' => 'تاريخ النشاط',
|
||||
'audit_date_from' => 'نطاق التاريخ من',
|
||||
'audit_date_to' => 'نطاق التاريخ إلى',
|
||||
@@ -166,11 +166,11 @@ return [
|
||||
// Role Settings
|
||||
'roles' => 'الأدوار',
|
||||
'role_user_roles' => 'أدوار المستخدمين',
|
||||
'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' => ':count user assigned|:count users assigned',
|
||||
'roles_x_permissions_provided' => ':count permission|:count permissions',
|
||||
'roles_assigned_users' => 'Assigned Users',
|
||||
'roles_permissions_provided' => 'Provided Permissions',
|
||||
'roles_index_desc' => 'تُستخدم الأدوار لتجميع المستخدمين ومنح أذونات النظام لأعضائها. عندما يكون المستخدم عضوًا في أدوار متعددة، تتراكم الصلاحيات الممنوحة، ويرث المستخدم جميع القدرات.',
|
||||
'roles_x_users_assigned' => ':count مستخدم معين|:count مستخدمين معينين',
|
||||
'roles_x_permissions_provided' => ':count إذن |:count إذونات',
|
||||
'roles_assigned_users' => 'المستخدمون المعينون',
|
||||
'roles_permissions_provided' => 'الصلاحيات المقدمة',
|
||||
'role_create' => 'إنشاء دور جديد',
|
||||
'role_delete' => 'حذف الدور',
|
||||
'role_delete_confirm' => 'سيتم حذف الدور المسمى \':roleName\'.',
|
||||
@@ -181,7 +181,7 @@ return [
|
||||
'role_details' => 'تفاصيل الدور',
|
||||
'role_name' => 'اسم الدور',
|
||||
'role_desc' => 'وصف مختصر للدور',
|
||||
'role_mfa_enforced' => 'Requires Multi-Factor Authentication',
|
||||
'role_mfa_enforced' => 'يتطلب مصادقة متعددة العوامل',
|
||||
'role_external_auth_id' => 'ربط الحساب بمواقع التواصل',
|
||||
'role_system' => 'أذونات النظام',
|
||||
'role_manage_users' => 'إدارة المستخدمين',
|
||||
@@ -191,15 +191,15 @@ return [
|
||||
'role_manage_page_templates' => 'إدارة قوالب الصفحة',
|
||||
'role_access_api' => 'الوصول إلى واجهة برمجة تطبيقات النظام API',
|
||||
'role_manage_settings' => 'إدارة إعدادات التطبيق',
|
||||
'role_export_content' => 'Export content',
|
||||
'role_import_content' => 'Import content',
|
||||
'role_editor_change' => 'Change page editor',
|
||||
'role_notifications' => 'Receive & manage notifications',
|
||||
'role_export_content' => 'تصدير المحتوى',
|
||||
'role_import_content' => 'استيراد المحتوى',
|
||||
'role_editor_change' => 'تغيير محرر الصفحة',
|
||||
'role_notifications' => 'تلقي الإشعارات وإدارتها',
|
||||
'role_asset' => 'أذونات الأصول',
|
||||
'roles_system_warning' => 'اعلم أن الوصول إلى أي من الأذونات الثلاثة المذكورة أعلاه يمكن أن يسمح للمستخدم بتغيير امتيازاته الخاصة أو امتيازات الآخرين في النظام. قم بتعيين الأدوار مع هذه الأذونات فقط للمستخدمين الموثوق بهم.',
|
||||
'role_asset_desc' => 'تتحكم هذه الأذونات في الوصول الافتراضي إلى الأصول داخل النظام. ستتجاوز الأذونات الخاصة بالكتب والفصول والصفحات هذه الأذونات.',
|
||||
'role_asset_admins' => 'يُمنح المسؤولين حق الوصول تلقائيًا إلى جميع المحتويات ولكن هذه الخيارات قد تعرض خيارات واجهة المستخدم أو تخفيها.',
|
||||
'role_asset_image_view_note' => 'This relates to visibility within the image manager. Actual access of uploaded image files will be dependant upon system image storage option.',
|
||||
'role_asset_image_view_note' => 'يتعلق هذا بالرؤية داخل مدير الصور. يعتمد الوصول الفعلي لملفات الصور المُحمّلة على خِيار تخزين الصور في النظام.',
|
||||
'role_all' => 'الكل',
|
||||
'role_own' => 'ما يخص',
|
||||
'role_controlled_by_asset' => 'يتحكم فيها الأصول التي يتم رفعها إلى',
|
||||
@@ -209,7 +209,7 @@ return [
|
||||
|
||||
// Users
|
||||
'users' => 'المستخدمون',
|
||||
'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.',
|
||||
'users_index_desc' => 'إنشاء وإدارة حسابات المستخدمين الفردية داخل النظام. يتم استخدام حسابات المستخدم لتسجيل الدخول وإسناد المحتوى والنشاط. صلاحيات الوصول هي أساسا قائمة على الأدوار ولكن ملكية محتوى المستخدم، من بين عوامل أخرى، قد تؤثر أيضا على صلاحيات والوصول إليها.',
|
||||
'user_profile' => 'ملف المستخدم',
|
||||
'users_add_new' => 'إضافة مستخدم جديد',
|
||||
'users_search' => 'بحث عن مستخدم',
|
||||
@@ -220,20 +220,20 @@ return [
|
||||
'users_role' => 'أدوار المستخدمين',
|
||||
'users_role_desc' => 'حدد الأدوار التي سيتم تعيين هذا المستخدم لها. إذا تم تعيين مستخدم لأدوار متعددة ، فسيتم تكديس الأذونات من هذه الأدوار وسيتلقى كل قدرات الأدوار المعينة.',
|
||||
'users_password' => 'كلمة مرور المستخدم',
|
||||
'users_password_desc' => 'Set a password used to log-in to the application. This must be at least 8 characters long.',
|
||||
'users_send_invite_text' => 'يمكنك اختيار إرسال دعوة بالبريد الإلكتروني إلى هذا المستخدم مما يسمح له بتعيين كلمة المرور الخاصة به أو يمكنك تعيين كلمة المرور الخاصة به بنفسك.',
|
||||
'users_password_desc' => 'عيّن كلمة سر لتسجيل الدخول إلى التطبيق. يجب ألا تقل عن 8 أحرف.',
|
||||
'users_send_invite_text' => 'يمكنك اختيار إرسال دعوة بالبريد الإلكتروني إلى هذا المستخدم مما يسمح له بتعيين كلمة السر الخاصة به أو يمكنك تعيين كلمة المرور الخاصة به بنفسك.',
|
||||
'users_send_invite_option' => 'أرسل بريدًا إلكترونيًا لدعوة المستخدم',
|
||||
'users_external_auth_id' => 'ربط الحساب بمواقع التواصل',
|
||||
'users_external_auth_id_desc' => 'When an external authentication system is in use (such as SAML2, OIDC or LDAP) this is the ID which links this BookStack user to the authentication system account. You can ignore this field if using the default email-based authentication.',
|
||||
'users_password_warning' => 'Only fill the below if you would like to change the password for this user.',
|
||||
'users_external_auth_id_desc' => 'عند استخدام نظام مصادقة خارجي (مثل SAML2 أو OIDC أو LDAP)، يكون هذا هو المعرف الذي يربط مستخدم بوكستاك -BookStack- بحساب نظام المصادقة. يمكنك تجاهل هذا الحقل عند استخدام المصادقة الافتراضية عبر البريد الإلكتروني.',
|
||||
'users_password_warning' => 'قم بملء الحقل أدناه فقط إذا كنت ترغب في تغيير كلمة السر لهذا المستخدم.',
|
||||
'users_system_public' => 'هذا المستخدم يمثل أي ضيف يقوم بزيارة شيء يخصك. لا يمكن استخدامه لتسجيل الدخول ولكن يتم تعيينه تلقائياً.',
|
||||
'users_delete' => 'حذف المستخدم',
|
||||
'users_delete_named' => 'حذف المستخدم :userName',
|
||||
'users_delete_warning' => 'سيتم حذف المستخدم \':userName\' بشكل تام من النظام.',
|
||||
'users_delete_confirm' => 'تأكيد حذف المستخدم؟',
|
||||
'users_migrate_ownership' => 'Migrate Ownership',
|
||||
'users_migrate_ownership_desc' => 'Select a user here if you want another user to become the owner of all items currently owned by this user.',
|
||||
'users_none_selected' => 'No user selected',
|
||||
'users_migrate_ownership' => 'نقل الملكية',
|
||||
'users_migrate_ownership_desc' => 'حدد مستخدم هنا إذا كنت تريد أن يصبح مستخدم آخر مالك جميع العناصر التي يمتلكها هذا المستخدم حاليا.',
|
||||
'users_none_selected' => 'لم يتم تحديد مستخدم',
|
||||
'users_edit' => 'تعديل المستخدم',
|
||||
'users_edit_profile' => 'تعديل الملف',
|
||||
'users_avatar' => 'صورة المستخدم',
|
||||
@@ -241,24 +241,24 @@ return [
|
||||
'users_preferred_language' => 'اللغة المفضلة',
|
||||
'users_preferred_language_desc' => 'سيؤدي هذا الخيار إلى تغيير اللغة المستخدمة لواجهة المستخدم الخاصة بالتطبيق. لن يؤثر هذا على أي محتوى قد أنشائه المستخدم.',
|
||||
'users_social_accounts' => 'الحسابات الاجتماعية',
|
||||
'users_social_accounts_desc' => 'View the status of the connected social accounts for this user. Social accounts can be used in addition to the primary authentication system for system access.',
|
||||
'users_social_accounts_desc' => 'عرض حالة الحسابات الاجتماعية المرتبطة لهذا المستخدم. ويمكن استخدام الحسابات الاجتماعية بالإضافة إلى نظام التوثيق الرئيس للوصول إلى النظام.',
|
||||
'users_social_accounts_info' => 'Here you can connect your other accounts for quicker and easier login. Disconnecting an account here does not previously authorized access. Revoke access from your profile settings on the connected social account.',
|
||||
'users_social_connect' => 'ربط الحساب',
|
||||
'users_social_disconnect' => 'فصل الحساب',
|
||||
'users_social_status_connected' => 'Connected',
|
||||
'users_social_status_disconnected' => 'Disconnected',
|
||||
'users_social_status_connected' => 'متصل',
|
||||
'users_social_status_disconnected' => 'غير متصل',
|
||||
'users_social_connected' => 'تم ربط حساب :socialAccount بملفك بنجاح.',
|
||||
'users_social_disconnected' => 'تم فصل حساب :socialAccount من ملفك بنجاح.',
|
||||
'users_api_tokens' => 'رموز الـ API',
|
||||
'users_api_tokens_desc' => 'Create and manage the access tokens used to authenticate with the BookStack REST API. Permissions for the API are managed via the user that the token belongs to.',
|
||||
'users_api_tokens_desc' => 'أنشئ وأدر رموز الوصول المستخدمة للمصادقة باستخدام واجهة برمجة تطبيقات بوكستاك رِست -BookStack REST API-. تتم إدارة صلاحيات واجهة برمجة التطبيقات بواسطة المستخدم الذي ينتمي إليه الرمز.',
|
||||
'users_api_tokens_none' => 'لم يتم إنشاء رموز API لهذا المستخدم',
|
||||
'users_api_tokens_create' => 'قم بإنشاء رمز مميز',
|
||||
'users_api_tokens_expires' => 'انتهاء مدة الصلاحية',
|
||||
'users_api_tokens_docs' => 'وثائق API',
|
||||
'users_mfa' => 'Multi-Factor Authentication',
|
||||
'users_mfa_desc' => 'Setup multi-factor authentication as an extra layer of security for your user account.',
|
||||
'users_mfa_x_methods' => ':count method configured|:count methods configured',
|
||||
'users_mfa_configure' => 'Configure Methods',
|
||||
'users_mfa' => 'المصادقة متعددة العوامل',
|
||||
'users_mfa_desc' => 'إعداد المصادقة متعددة العوامل كطبقة إضافية من الأمان لحساب المستخدم الخاص بك.',
|
||||
'users_mfa_x_methods' => ':count طريقة مُهيأة | :count طرق مُهيأة',
|
||||
'users_mfa_configure' => 'إعداد الطرق',
|
||||
|
||||
// API Tokens
|
||||
'user_api_token_create' => 'قم بإنشاء رمز API',
|
||||
@@ -279,42 +279,42 @@ return [
|
||||
'user_api_token_delete_confirm' => 'هل أنت متأكد من أنك تريد حذف رمز API؟',
|
||||
|
||||
// Webhooks
|
||||
'webhooks' => 'Webhooks',
|
||||
'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' => ':count trigger event|:count trigger events',
|
||||
'webhooks_create' => 'Create New Webhook',
|
||||
'webhooks_none_created' => 'No webhooks have yet been created.',
|
||||
'webhooks_edit' => 'Edit Webhook',
|
||||
'webhooks_save' => 'Save Webhook',
|
||||
'webhooks_details' => 'Webhook Details',
|
||||
'webhooks_details_desc' => 'Provide a user friendly name and a POST endpoint as a location for the webhook data to be sent to.',
|
||||
'webhooks_events' => 'Webhook Events',
|
||||
'webhooks_events_desc' => 'Select all the events that should trigger this webhook to be called.',
|
||||
'webhooks_events_warning' => 'Keep in mind that these events will be triggered for all selected events, even if custom permissions are applied. Ensure that use of this webhook won\'t expose confidential content.',
|
||||
'webhooks_events_all' => 'All system events',
|
||||
'webhooks_name' => 'Webhook Name',
|
||||
'webhooks_timeout' => 'Webhook Request Timeout (Seconds)',
|
||||
'webhooks_endpoint' => 'Webhook Endpoint',
|
||||
'webhooks_active' => 'Webhook Active',
|
||||
'webhook_events_table_header' => 'Events',
|
||||
'webhooks_delete' => 'Delete Webhook',
|
||||
'webhooks_delete_warning' => 'This will fully delete this webhook, with the name \':webhookName\', from the system.',
|
||||
'webhooks_delete_confirm' => 'Are you sure you want to delete this webhook?',
|
||||
'webhooks_format_example' => 'Webhook Format Example',
|
||||
'webhooks_format_example_desc' => 'Webhook data is sent as a POST request to the configured endpoint as JSON following the format below. The "related_item" and "url" properties are optional and will depend on the type of event triggered.',
|
||||
'webhooks_status' => 'Webhook Status',
|
||||
'webhooks_last_called' => 'Last Called:',
|
||||
'webhooks_last_errored' => 'Last Errored:',
|
||||
'webhooks_last_error_message' => 'Last Error Message:',
|
||||
'webhooks' => 'خطافات الويب -Webhooks-',
|
||||
'webhooks_index_desc' => 'خطافات الويب هي طريقة لإرسال البيانات إلى الروابط الخارجية عندما تحدث بعض الإجراءات والأحداث داخل النظام الذي يسمح بالتكامل القائم على الأحداث مع المنصات الخارجية مثل نظم المراسلة أو الإشعار.',
|
||||
'webhooks_x_trigger_events' => ':count حدث تشغيل |:count أحداث تشغيل',
|
||||
'webhooks_create' => 'إنشاء خطاف ويب جديد',
|
||||
'webhooks_none_created' => 'لم يتم إنشاء أي خطافات ويب حتى الآن.',
|
||||
'webhooks_edit' => 'تحرير خطاف ويب',
|
||||
'webhooks_save' => 'حفظ خطاف ويب',
|
||||
'webhooks_details' => 'تفاصيل خطاف الويب',
|
||||
'webhooks_details_desc' => 'قم بتوفير اسم سهل الاستخدام ونقطة نهاية POST كموقع لإرسال بيانات خطافات الويب إليه.',
|
||||
'webhooks_events' => 'أحداث خطفات الويب',
|
||||
'webhooks_events_desc' => 'حدد جميع الأحداث التي يجب أن تشغل هذا الرابط ليتم استدعاؤها.',
|
||||
'webhooks_events_warning' => 'ضع في اعتبارك أن هذه الأحداث سيتم تشغيلها لجميع الأحداث المحددة، حتى إذا تم تطبيق صلاحيات مخصصة. تحقق أن استخدام خطاف الويب هذا لن يكشف عن محتوى سري.',
|
||||
'webhooks_events_all' => 'جميع أحداث النظام',
|
||||
'webhooks_name' => 'اسم خطاف الويب',
|
||||
'webhooks_timeout' => 'مهلة طلب خطاف الويب (بالثواني)',
|
||||
'webhooks_endpoint' => 'نقطة نهاية خطاف الويب',
|
||||
'webhooks_active' => 'خطاف الويب فعال',
|
||||
'webhook_events_table_header' => 'الأحداث',
|
||||
'webhooks_delete' => 'حذف خطاف الويب',
|
||||
'webhooks_delete_warning' => 'سيؤدي هذا إلى حذف خطاف الويب بالكامل، الذي يحمل اسم \':webhookName\'، من النظام.',
|
||||
'webhooks_delete_confirm' => 'هل أنت متيقِّن أنك تريد حذف هذا الخطاف؟',
|
||||
'webhooks_format_example' => 'مثال على تنسيق خطاف الويب',
|
||||
'webhooks_format_example_desc' => 'يتم إرسال بيانات خطاف الويب كطلب بوست -POST- إلى نقطة النهاية المكونة كجيسون -JSON- باتباع التنسيق أدناه. خصائص "ذات صلة" و "روابط" اختيارية و ستعتمد على نوع الحدث الذي تم تشغيله.',
|
||||
'webhooks_status' => 'حالة خطاف الويب',
|
||||
'webhooks_last_called' => 'آخر اتصال:',
|
||||
'webhooks_last_errored' => 'أخر خطأ:',
|
||||
'webhooks_last_error_message' => 'رسالة الخطأ الأخيرة:',
|
||||
|
||||
// Licensing
|
||||
'licenses' => 'Licenses',
|
||||
'licenses_desc' => 'This page details license information for BookStack in addition to the projects & libraries that are used within BookStack. Many projects listed may only be used in a development context.',
|
||||
'licenses_bookstack' => 'BookStack License',
|
||||
'licenses_php' => 'PHP Library Licenses',
|
||||
'licenses_js' => 'JavaScript Library Licenses',
|
||||
'licenses_other' => 'Other Licenses',
|
||||
'license_details' => 'License Details',
|
||||
'licenses' => 'الرخص',
|
||||
'licenses_desc' => 'هذه الصفحة تفصل معلومات الرخص لبوكستاك -BookStack- بالإضافة إلى المشاريع والمكتبات المستخدمة في بوكستاك. ولا يمكن استخدام العديد من المشاريع المدرجة إلا في سياق إنمائي.',
|
||||
'licenses_bookstack' => 'رخص بوكستاك',
|
||||
'licenses_php' => 'رخص مكتبات بي إتش بي -PHP-',
|
||||
'licenses_js' => 'رخص مكتبة جافا سكريبت -JavaScript-',
|
||||
'licenses_other' => 'رخص أخرى',
|
||||
'license_details' => 'تفاصيل الرخصة',
|
||||
|
||||
//! If editing translations files directly please ignore this in all
|
||||
//! languages apart from en. Content will be auto-copied from en.
|
||||
@@ -348,6 +348,7 @@ return [
|
||||
'lt' => 'Lietuvių Kalba',
|
||||
'lv' => 'Latviešu Valoda',
|
||||
'nb' => 'Norsk (Bokmål)',
|
||||
'ne' => 'नेपाली',
|
||||
'nn' => 'Nynorsk',
|
||||
'nl' => 'Nederlands',
|
||||
'pl' => 'Polski',
|
||||
|
||||
@@ -105,15 +105,15 @@ return [
|
||||
'url' => 'صيغة :attribute غير صالحة.',
|
||||
'uploaded' => 'تعذر تحميل الملف. قد لا يقبل الخادم ملفات بهذا الحجم.',
|
||||
|
||||
'zip_file' => 'The :attribute needs to reference a file within the ZIP.',
|
||||
'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.',
|
||||
'zip_model_expected' => 'Data object expected but ":type" found.',
|
||||
'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.',
|
||||
'zip_file' => ':attribute بحاجة إلى الرجوع إلى مِلَفّ داخل المِلَفّ المضغوط.',
|
||||
'zip_file_mime' => ':attribute بحاجة إلى الإشارة إلى مِلَفّ من نوع :validTypes، وجدت :foundType.',
|
||||
'zip_model_expected' => 'عنصر البيانات المتوقع ولكن ":type" تم العثور عليه.',
|
||||
'zip_unique' => 'يجب أن يكون :attribute فريداً لنوع الكائن داخل المِلَفّ المضغوط.',
|
||||
|
||||
// Custom validation lines
|
||||
'custom' => [
|
||||
'password-confirm' => [
|
||||
'required_with' => 'يجب تأكيد كلمة المرور',
|
||||
'required_with' => 'يجب تأكيد كلمة السر',
|
||||
],
|
||||
],
|
||||
|
||||
|
||||
@@ -30,6 +30,8 @@ return [
|
||||
'create' => 'Създаване',
|
||||
'update' => 'Обновяване',
|
||||
'edit' => 'Редактиране',
|
||||
'archive' => 'Archive',
|
||||
'unarchive' => 'Un-Archive',
|
||||
'sort' => 'Сортиране',
|
||||
'move' => 'Преместване',
|
||||
'copy' => 'Копиране',
|
||||
|
||||
@@ -48,6 +48,7 @@ return [
|
||||
'superscript' => 'Горен индекс',
|
||||
'subscript' => 'Долен индекс',
|
||||
'text_color' => 'Цвят на текста',
|
||||
'highlight_color' => 'Highlight color',
|
||||
'custom_color' => 'Цвят по избор',
|
||||
'remove_color' => 'Премахване на цвят',
|
||||
'background_color' => 'Фонов цвят',
|
||||
|
||||
@@ -248,7 +248,7 @@ return [
|
||||
'pages_edit_switch_to_markdown_stable' => '(Stable Content)',
|
||||
'pages_edit_switch_to_wysiwyg' => 'Switch to WYSIWYG Editor',
|
||||
'pages_edit_switch_to_new_wysiwyg' => 'Switch to new WYSIWYG',
|
||||
'pages_edit_switch_to_new_wysiwyg_desc' => '(In Alpha Testing)',
|
||||
'pages_edit_switch_to_new_wysiwyg_desc' => '(In Beta Testing)',
|
||||
'pages_edit_set_changelog' => 'Задайте регистър на промените',
|
||||
'pages_edit_enter_changelog_desc' => 'Въведете кратко резюме на промените, които сте създали',
|
||||
'pages_edit_enter_changelog' => 'Въведи регистър на промените',
|
||||
@@ -268,6 +268,7 @@ return [
|
||||
'pages_md_insert_drawing' => 'Вмъкни рисунка',
|
||||
'pages_md_show_preview' => 'Show preview',
|
||||
'pages_md_sync_scroll' => 'Sync preview scroll',
|
||||
'pages_md_plain_editor' => 'Plaintext editor',
|
||||
'pages_drawing_unsaved' => 'Unsaved Drawing Found',
|
||||
'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?',
|
||||
'pages_not_in_chapter' => 'Страницата не принадлежи в никоя глава',
|
||||
@@ -392,8 +393,11 @@ return [
|
||||
'comment' => 'Коментирай',
|
||||
'comments' => 'Коментари',
|
||||
'comment_add' => 'Добавяне на коментар',
|
||||
'comment_none' => 'No comments to display',
|
||||
'comment_placeholder' => 'Напишете коментар',
|
||||
'comment_count' => '{0} Няма коментари|{1} 1 коментар|[2,*] :count коментара',
|
||||
'comment_thread_count' => ':count Comment Thread|:count Comment Threads',
|
||||
'comment_archived_count' => ':count Archived',
|
||||
'comment_archived_threads' => 'Archived Threads',
|
||||
'comment_save' => 'Запази коментар',
|
||||
'comment_new' => 'Нов коментар',
|
||||
'comment_created' => 'коментирано :createDiff',
|
||||
@@ -402,8 +406,14 @@ return [
|
||||
'comment_deleted_success' => 'Коментарът е изтрит',
|
||||
'comment_created_success' => 'Коментарът е добавен',
|
||||
'comment_updated_success' => 'Коментарът е обновен',
|
||||
'comment_archive_success' => 'Comment archived',
|
||||
'comment_unarchive_success' => 'Comment un-archived',
|
||||
'comment_view' => 'View comment',
|
||||
'comment_jump_to_thread' => 'Jump to thread',
|
||||
'comment_delete_confirm' => 'Наистина ли искате да изтриете този коментар?',
|
||||
'comment_in_reply_to' => 'В отговор на :commentId',
|
||||
'comment_reference' => 'Reference',
|
||||
'comment_reference_outdated' => '(Outdated)',
|
||||
'comment_editor_explain' => 'Here are the comments that have been left on this page. Comments can be added & managed when viewing the saved page.',
|
||||
|
||||
// Revision
|
||||
|
||||
@@ -348,6 +348,7 @@ return [
|
||||
'lt' => 'Lietuvių Kalba',
|
||||
'lv' => 'Latviešu Valoda',
|
||||
'nb' => 'Norsk (Bokmål)',
|
||||
'ne' => 'नेपाली',
|
||||
'nn' => 'Nynorsk',
|
||||
'nl' => 'Nederlands',
|
||||
'pl' => 'Polski',
|
||||
|
||||
@@ -30,6 +30,8 @@ return [
|
||||
'create' => 'তৈরী করুন',
|
||||
'update' => 'হালনাগাদ করুন',
|
||||
'edit' => 'সম্পাদন করুন',
|
||||
'archive' => 'Archive',
|
||||
'unarchive' => 'Un-Archive',
|
||||
'sort' => 'ক্রমান্বয় করুন',
|
||||
'move' => 'স্থানান্তর করুন',
|
||||
'copy' => 'অনুলিপি করুন',
|
||||
|
||||
@@ -48,6 +48,7 @@ return [
|
||||
'superscript' => 'Superscript',
|
||||
'subscript' => 'Subscript',
|
||||
'text_color' => 'Text color',
|
||||
'highlight_color' => 'Highlight color',
|
||||
'custom_color' => 'Custom color',
|
||||
'remove_color' => 'Remove color',
|
||||
'background_color' => 'Background color',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user