Merge branch 'development' into release

This commit is contained in:
Dan Brown
2022-08-11 15:16:34 +01:00
31 changed files with 446 additions and 364 deletions

View File

@ -56,6 +56,7 @@ Name :: Languages
@arcoai :: Spanish
@Jokuna :: Korean
@smartshogu :: German; German Informal
@samadha56 :: Persian
cipi1965 :: Italian
Mykola Ronik (Mantikor) :: Ukrainian
furkanoyk :: Turkish
@ -268,3 +269,4 @@ mcgong (GongMingCai) :: Chinese Simplified; Chinese Traditional
Nanang Setia Budi (sefidananang) :: Indonesian
Андрей Павлов (andrei.pavlov) :: Russian
Alex Navarro (alex.n.navarro) :: Portuguese, Brazilian
Ji-Hyeon Gim (PotatoGim) :: Korean

View File

@ -34,7 +34,13 @@ class PermissionApplicator
$ownRolePermission = $user->can($fullPermission . '-own');
$nonJointPermissions = ['restrictions', 'image', 'attachment', 'comment'];
$ownerField = ($ownable instanceof Entity) ? 'owned_by' : 'created_by';
$isOwner = $user->id === $ownable->getAttribute($ownerField);
$ownableFieldVal = $ownable->getAttribute($ownerField);
if (is_null($ownableFieldVal)) {
throw new InvalidArgumentException("{$ownerField} field used but has not been loaded");
}
$isOwner = $user->id === $ownableFieldVal;
$hasRolePermission = $allRolePermission || ($isOwner && $ownRolePermission);
// Handle non entity specific jointPermissions
@ -68,6 +74,11 @@ class PermissionApplicator
}
foreach ($chain as $currentEntity) {
if (is_null($currentEntity->restricted)) {
throw new InvalidArgumentException("Entity restricted field used but has not been loaded");
}
if ($currentEntity->restricted) {
return $currentEntity->permissions()
->whereIn('role_id', $userRoleIds)

View File

@ -80,6 +80,11 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
*/
protected ?Collection $permissions;
/**
* This holds the user's avatar URL when loaded to prevent re-calculating within the same request.
*/
protected string $avatarUrl = '';
/**
* This holds the default user when loaded.
*
@ -233,12 +238,17 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
return $default;
}
if (!empty($this->avatarUrl)) {
return $this->avatarUrl;
}
try {
$avatar = $this->avatar ? url($this->avatar->getThumb($size, $size, false)) : $default;
} catch (Exception $err) {
$avatar = $default;
}
$this->avatarUrl = $avatar;
return $avatar;
}

View File

@ -38,6 +38,7 @@ class BaseRepo
$this->tagRepo->saveTagsToEntity($entity, $input['tags']);
}
$entity->refresh();
$entity->rebuildPermissions();
$entity->indexForSearch();
}

View File

@ -140,7 +140,7 @@ class BookshelfRepo
public function copyDownPermissions(Bookshelf $shelf, $checkUserPermissions = true): int
{
$shelfPermissions = $shelf->permissions()->get(['role_id', 'action'])->toArray();
$shelfBooks = $shelf->books()->get(['id', 'restricted']);
$shelfBooks = $shelf->books()->get(['id', 'restricted', 'owned_by']);
$updatedBookCount = 0;
/** @var Book $book */

View File

@ -163,7 +163,7 @@ class SearchRunner
$entityQuery = $entityModelInstance->newQuery()->scopes('visible');
if ($entityModelInstance instanceof Page) {
$entityQuery->select($entityModelInstance::$listAttributes);
$entityQuery->select(array_merge($entityModelInstance::$listAttributes, ['restricted', 'owned_by']));
} else {
$entityQuery->select(['*']);
}

View File

@ -86,6 +86,9 @@ class PageApiController extends ApiController
*
* Pages will always have HTML content. They may have markdown content
* if the markdown editor was used to last update the page.
*
* See the "Content Security" section of these docs for security considerations when using
* the page content returned from this endpoint.
*/
public function read(string $id)
{

View File

@ -87,7 +87,7 @@ class FavouriteController extends Controller
$modelInstance = $model->newQuery()
->where('id', '=', $modelInfo['id'])
->first(['id', 'name']);
->first(['id', 'name', 'restricted', 'owned_by']);
$inaccessibleEntity = ($modelInstance instanceof Entity && !userCan('view', $modelInstance));
if (is_null($modelInstance) || $inaccessibleEntity) {

View File

@ -11,11 +11,8 @@ use Ssddanbrown\HtmlDiff\Diff;
class PageRevisionController extends Controller
{
protected $pageRepo;
protected PageRepo $pageRepo;
/**
* PageRevisionController constructor.
*/
public function __construct(PageRepo $pageRepo)
{
$this->pageRepo = $pageRepo;
@ -29,11 +26,19 @@ class PageRevisionController extends Controller
public function index(string $bookSlug, string $pageSlug)
{
$page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
$this->setPageTitle(trans('entities.pages_revisions_named', ['pageName'=>$page->getShortName()]));
$revisions = $page->revisions()->select([
'id', 'page_id', 'name', 'created_at', 'created_by', 'updated_at',
'type', 'revision_number', 'summary',
])
->selectRaw("IF(markdown = '', false, true) as is_markdown")
->with(['page.book', 'createdBy'])
->get();
$this->setPageTitle(trans('entities.pages_revisions_named', ['pageName' => $page->getShortName()]));
return view('pages.revisions', [
'revisions' => $revisions,
'page' => $page,
'current' => $page,
]);
}

View File

@ -45,6 +45,11 @@ class HtmlContentFilter
$badIframes = $xPath->query('//*[' . static::xpathContains('@src', 'data:') . '] | //*[' . static::xpathContains('@src', 'javascript:') . '] | //*[@srcdoc]');
static::removeNodes($badIframes);
// Remove tags hiding JavaScript or data uris in values attribute.
// For example, SVG animate tag can exploit javascript in values.
$badValuesTags = $xPath->query('//*[' . static::xpathContains('@values', 'data:') . '] | //*[' . static::xpathContains('@values', 'javascript:') . ']');
static::removeNodes($badValuesTags);
// Remove elements with a xlink:href attribute
// Used in SVG but deprecated anyway, so we'll be a bit more heavy-handed here.
$xlinkHrefAttributes = $xPath->query('//@*[contains(name(), \'xlink:href\')]');

View File

@ -111,10 +111,8 @@ function defineCodeBlockCustomElement(editor) {
const container = this.shadowRoot.querySelector('.CodeMirrorContainer');
const renderCodeMirror = (Code) => {
this.cm = Code.wysiwygView(container, content, this.getLanguage());
Code.updateLayout(this.cm);
setTimeout(() => {
this.style.height = null;
}, 1);
setTimeout(() => Code.updateLayout(this.cm), 10);
setTimeout(() => this.style.height = null, 12);
};
window.importVersioned('code').then((Code) => {

View File

@ -28,8 +28,8 @@ return [
// Books
'book_create' => 'ایجاد کتاب',
'book_create_notification' => 'کتاب با موفقیت ایجاد شد',
'book_create_from_chapter' => 'converted chapter to book',
'book_create_from_chapter_notification' => 'Chapter successfully converted to a book',
'book_create_from_chapter' => 'تبدیل فصل به کتاب',
'book_create_from_chapter_notification' => 'فصل با موفقیت به یک کتاب تبدیل شد',
'book_update' => 'به روزرسانی کتاب',
'book_update_notification' => 'کتاب با موفقیت به روزرسانی شد',
'book_delete' => 'حذف کتاب',
@ -38,10 +38,10 @@ return [
'book_sort_notification' => 'کتاب با موفقیت مرتب سازی شد',
// Bookshelves
'bookshelf_create' => 'ایجاد قفسه کتاب',
'bookshelf_create_notification' => 'قفسه کتاب با موفقیت ایجاد شد',
'bookshelf_create_from_book' => 'converted book to bookshelf',
'bookshelf_create_from_book_notification' => 'Book successfully converted to a shelf',
'bookshelf_create' => 'ایجاد قفسه کتاب',
'bookshelf_create_notification' => 'قفسه کتاب با موفقیت ایجاد شد',
'bookshelf_create_from_book' => 'تبدیل کتاب به قفسه',
'bookshelf_create_from_book_notification' => 'کتاب با موفقیت به یک قفسه تبدیل شد',
'bookshelf_update' => 'به روزرسانی قفسه کتاب',
'bookshelf_update_notification' => 'قفسه کتاب با موفقیت به روزرسانی شد',
'bookshelf_delete' => 'حذف قفسه کتاب',

View File

@ -42,13 +42,13 @@ return [
'configure' => 'پیکربندی کنید',
'fullscreen' => 'تمام صفحه',
'favourite' => 'علاقه‌مندی',
'unfavourite' => 'Unfavourite',
'unfavourite' => 'حذف از علاقه‌مندی',
'next' => 'بعدی',
'previous' => 'قبلى',
'filter_active' => 'فیلتر فعال:',
'filter_clear' => 'پاک کردن فیلتر',
'download' => 'Download',
'open_in_tab' => 'Open in Tab',
'download' => 'دانلود',
'open_in_tab' => 'باز کردن در تب جدید',
// Sort Options
'sort_options' => 'گزینه‌های مرتب سازی',
@ -56,7 +56,7 @@ return [
'sort_ascending' => 'مرتب‌سازی صعودی',
'sort_descending' => 'مرتب‌سازی نزولی',
'sort_name' => 'نام',
'sort_default' => 'پیشفرض',
'sort_default' => 'پیشفرض',
'sort_created_at' => 'تاریخ ایجاد',
'sort_updated_at' => 'تاریخ بروزرسانی',
@ -71,7 +71,7 @@ return [
'details' => 'جزییات',
'grid_view' => 'نمایش شبکه‌ای',
'list_view' => 'نمای لیست',
'default' => 'پیشفرض',
'default' => 'پیشفرض',
'breadcrumb' => 'مسیر جاری',
'status' => 'وضعیت',
'status_active' => 'فعال',

View File

@ -24,7 +24,7 @@ return [
'width' => 'عرض',
'height' => 'ارتفاع',
'More' => 'بیشتر',
'select' => 'Select...',
'select' => 'انتخاب...',
// Toolbar
'formats' => 'الگو',
@ -35,7 +35,7 @@ return [
'paragraph' => 'پاراگراف',
'blockquote' => 'نقل قول',
'inline_code' => 'کد درون خطی',
'callouts' => 'تعليق تفسيري',
'callouts' => 'قالب پیام و هشدار',
'callout_information' => 'اطلاعات',
'callout_success' => 'موفق',
'callout_warning' => 'هشدار',
@ -44,128 +44,128 @@ return [
'italic' => 'حروف کج(ایتالیک)',
'underline' => 'زیرخط',
'strikethrough' => 'خط خورده',
'superscript' => 'Superscript',
'subscript' => 'Subscript',
'text_color' => 'Text color',
'custom_color' => 'Custom color',
'remove_color' => 'Remove color',
'background_color' => 'Background color',
'align_left' => 'Align left',
'align_center' => 'Align center',
'align_right' => 'Align right',
'align_justify' => 'Justify',
'list_bullet' => 'Bullet list',
'list_numbered' => 'Numbered list',
'list_task' => 'Task list',
'indent_increase' => 'Increase indent',
'indent_decrease' => 'Decrease indent',
'table' => 'Table',
'insert_image' => 'Insert image',
'insert_image_title' => 'Insert/Edit Image',
'insert_link' => 'Insert/edit link',
'insert_link_title' => 'Insert/Edit Link',
'insert_horizontal_line' => 'Insert horizontal line',
'insert_code_block' => 'Insert code block',
'insert_drawing' => 'Insert/edit drawing',
'drawing_manager' => 'Drawing manager',
'insert_media' => 'Insert/edit media',
'insert_media_title' => 'Insert/Edit Media',
'clear_formatting' => 'Clear formatting',
'source_code' => 'Source code',
'source_code_title' => 'Source Code',
'fullscreen' => 'Fullscreen',
'image_options' => 'Image options',
'superscript' => 'بالانویسی',
'subscript' => 'پایین نویسی',
'text_color' => 'رنگ متن',
'custom_color' => 'رنگ دلخواه',
'remove_color' => 'حذف رنگ',
'background_color' => 'رنگ زمینه',
'align_left' => 'چپ چین',
'align_center' => 'وسط چین',
'align_right' => 'راست چین',
'align_justify' => 'همتراز',
'list_bullet' => 'لیست نشانه دار',
'list_numbered' => 'لیست عددی',
'list_task' => 'لیست کار',
'indent_increase' => 'افزایش تورفتگی',
'indent_decrease' => 'کاهش تورفتگی',
'table' => 'جدول',
'insert_image' => 'افزودن تصویر',
'insert_image_title' => 'افزودن/ویرایش تصویر',
'insert_link' => 'افزودن/ویرایش پیوند',
'insert_link_title' => 'افزودن/ویرایش پیوند',
'insert_horizontal_line' => 'افزودن خط افقی',
'insert_code_block' => 'افزودن بلوک کد',
'insert_drawing' => 'افزودن/ویرایش طرح',
'drawing_manager' => 'مدیریت طراحی',
'insert_media' => 'افزودن/ویرایش رسانه',
'insert_media_title' => 'افزودن/ویرایش رسانه',
'clear_formatting' => 'حذف قالب بندی',
'source_code' => 'کد منبع',
'source_code_title' => 'کد منبع',
'fullscreen' => 'تمام صفحه',
'image_options' => 'تنظیمات تصویر',
// Tables
'table_properties' => 'Table properties',
'table_properties_title' => 'Table Properties',
'delete_table' => 'Delete table',
'insert_row_before' => 'Insert row before',
'insert_row_after' => 'Insert row after',
'delete_row' => 'Delete row',
'insert_column_before' => 'Insert column before',
'insert_column_after' => 'Insert column after',
'delete_column' => 'Delete column',
'table_cell' => 'Cell',
'table_row' => 'Row',
'table_column' => 'Column',
'cell_properties' => 'Cell properties',
'cell_properties_title' => 'Cell Properties',
'cell_type' => 'Cell type',
'cell_type_cell' => 'Cell',
'cell_scope' => 'Scope',
'cell_type_header' => 'Header cell',
'merge_cells' => 'Merge cells',
'split_cell' => 'Split cell',
'table_row_group' => 'Row Group',
'table_column_group' => 'Column Group',
'horizontal_align' => 'Horizontal align',
'vertical_align' => 'Vertical align',
'border_width' => 'Border width',
'border_style' => 'Border style',
'border_color' => 'Border color',
'row_properties' => 'Row properties',
'row_properties_title' => 'Row Properties',
'cut_row' => 'Cut row',
'copy_row' => 'Copy row',
'paste_row_before' => 'Paste row before',
'paste_row_after' => 'Paste row after',
'row_type' => 'Row type',
'row_type_header' => 'Header',
'row_type_body' => 'Body',
'row_type_footer' => 'Footer',
'alignment' => 'Alignment',
'cut_column' => 'Cut column',
'copy_column' => 'Copy column',
'paste_column_before' => 'Paste column before',
'paste_column_after' => 'Paste column after',
'cell_padding' => 'Cell padding',
'cell_spacing' => 'Cell spacing',
'caption' => 'Caption',
'show_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',
'table_properties' => 'تنظیمات جدول',
'table_properties_title' => 'تنظیمات جدول',
'delete_table' => 'حذف جدول',
'insert_row_before' => 'افزودن سطر به قبل',
'insert_row_after' => 'افزودن سطر به بعد',
'delete_row' => 'حذف سطر',
'insert_column_before' => 'افزودن ستون به قبل',
'insert_column_after' => 'افزودن ستون به بعد',
'delete_column' => 'حذف ستون',
'table_cell' => 'سلول',
'table_row' => 'سطر',
'table_column' => 'ستون',
'cell_properties' => 'تنظیمات سلول',
'cell_properties_title' => 'تنظیمات سلول',
'cell_type' => 'نوع سلول',
'cell_type_cell' => 'سلول',
'cell_scope' => 'محدوده',
'cell_type_header' => 'بالای سلول',
'merge_cells' => 'ادغام سلول ها',
'split_cell' => 'جداسازی سلول ها',
'table_row_group' => 'گروه بندی سطر',
'table_column_group' => 'گروه بندی ستون',
'horizontal_align' => 'تراز افقی',
'vertical_align' => 'تراز عمودی',
'border_width' => 'پهنای حاشیه',
'border_style' => 'سبک حاشیه',
'border_color' => 'رنگ حاشیه',
'row_properties' => 'تنظیمات سطر',
'row_properties_title' => 'تنظیمات سطر',
'cut_row' => 'برش سطر',
'copy_row' => 'کپی سطر',
'paste_row_before' => 'چسباندن سطر به قبل',
'paste_row_after' => 'چسباندن سطر به بعد',
'row_type' => 'نوع سطر',
'row_type_header' => 'سربرگ',
'row_type_body' => 'بدنه',
'row_type_footer' => 'پانوشت',
'alignment' => 'ترازبندی',
'cut_column' => 'برش ستون',
'copy_column' => 'کپی ستون',
'paste_column_before' => 'چسباندن ستون به قبل',
'paste_column_after' => 'چسباندن ستون به بعد',
'cell_padding' => 'حاشیه سلول',
'cell_spacing' => 'فاصله سلول',
'caption' => 'عنوان',
'show_caption' => 'مشاهده عنوان',
'constrain' => 'محدودسازی نسبت ها',
'cell_border_solid' => 'یکپارچه',
'cell_border_dotted' => 'نقطه چین',
'cell_border_dashed' => 'خط چین',
'cell_border_double' => 'دوتایی',
'cell_border_groove' => 'شیار',
'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',
'open_link' => 'Open link in...',
'open_link_current' => 'Current window',
'open_link_new' => 'New window',
'insert_collapsible' => 'Insert collapsible block',
'collapsible_unwrap' => 'Unwrap',
'edit_label' => 'Edit label',
'toggle_open_closed' => 'Toggle open/closed',
'collapsible_edit' => 'Edit collapsible block',
'toggle_label' => 'Toggle label',
'source' => 'منبع',
'alt_desc' => 'توضیحات جایگزین',
'embed' => 'درج',
'paste_embed' => 'کد درج را در زیر وارد نمایید:',
'url' => 'آدرس',
'text_to_display' => 'متن جهت نمایش',
'title' => 'عنوان',
'open_link' => 'باز کردن لینک در ...',
'open_link_current' => 'پنجره کنونی',
'open_link_new' => 'پنجره جدید',
'insert_collapsible' => 'درج بلوک جمع شونده',
'collapsible_unwrap' => 'باز کردن',
'edit_label' => 'ویرایش برچسب',
'toggle_open_closed' => 'بستن/بازکردن',
'collapsible_edit' => 'ویرایش بلوک جمع شونده',
'toggle_label' => 'تغییر برچسب',
// About view
'about' => 'About the editor',
'about_title' => 'About the WYSIWYG Editor',
'editor_license' => 'Editor License & Copyright',
'editor_tiny_license' => 'This editor is built using :tinyLink which is provided under the MIT license.',
'editor_tiny_license_link' => 'The copyright and license details of TinyMCE can be found here.',
'save_continue' => 'Save Page & Continue',
'callouts_cycle' => '(Keep pressing to toggle through types)',
'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_tiny_license' => 'این ویرایشگر توسط :tinyLink و تحت مجوز MIT ساخته شده است.',
'editor_tiny_license_link' => 'جزئیات کپی رایت و مجوز TinyMCE را می توانید در اینجا پیدا کنید.',
'save_continue' => 'ذخیره صفحه و ادامه',
'callouts_cycle' => '(جهت تغییر نوع ها چندین بار فشار دهید)',
'link_selector' => 'پیوند به محتوا',
'shortcuts' => 'میانبرها',
'shortcut' => 'میانبر',
'shortcuts_intro' => 'میانبرهای قابل استفاده در این ویرایشگر:',
'windows_linux' => '(ویندوز/لینوکس)',
'mac' => '(مک)',
'description' => 'توضیحات',
];

View File

@ -48,7 +48,7 @@ return [
// Search
'search_results' => 'نتایج جستجو',
'search_total_results_found' => ':count result found|:count total results found',
'search_total_results_found' => 'نتیجه یافت شد :count | نتایج یافت شده :count',
'search_clear' => 'پاک کردن جستجو',
'search_no_pages' => 'هیچ صفحه ای با این جستجو مطابقت ندارد',
'search_for_term' => 'جستجو برای :term',
@ -79,7 +79,7 @@ return [
'x_shelves' => ':count تاقچه|:count تاقچه',
'shelves_long' => 'قفسه کتاب',
'shelves_empty' => 'هیچ قفسه ای ایجاد نشده است',
'shelves_create' => 'Create New Shelf',
'shelves_create' => 'ایجاد قفسه جدید',
'shelves_popular' => 'قفسه های محبوب',
'shelves_new' => 'قفسه های جدید',
'shelves_new_action' => 'قفسه جدید',
@ -104,7 +104,7 @@ return [
'shelves_copy_permissions_to_books' => 'کپی مجوزها در کتابها',
'shelves_copy_permissions' => 'مجوزهای کپی',
'shelves_copy_permissions_explain' => 'با این کار تنظیمات مجوز فعلی این قفسه کتاب برای همه کتاب‌های موجود در آن اعمال می‌شود. قبل از فعال کردن، مطمئن شوید که هر گونه تغییر در مجوزهای این قفسه کتاب ذخیره شده است.',
'shelves_copy_permission_success' => 'مجوزهای قفسه کتاب در :count books کپی شد',
'shelves_copy_permission_success' => 'مجوزهای قفسه کتاب در :count کتاب کپی شد',
// Books
'book' => 'کتاب',
@ -359,14 +359,14 @@ return [
'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' => 'آیا از تبدیل این فصل مطمئن هستید؟',
];

View File

@ -28,7 +28,7 @@ return [
'app_secure_images_toggle' => 'آپلود تصویر با امنیت بالاتر',
'app_secure_images_desc' => 'به دلایل عملکرد، همه تصاویر عمومی هستند. این گزینه یک رشته تصادفی و غیرقابل حدس زدن را در مقابل آدرس های تصویر اضافه می کند. برای جلوگیری از دسترسی آسان، اطمینان حاصل کنید که فهرست های دایرکتوری فعال نیستند.',
'app_default_editor' => 'ویرایشگر پیش فرض صفحه',
'app_default_editor_desc' => 'Select which editor will be used by default when editing new pages. This can be overridden at a page level where permissions allow.',
'app_default_editor_desc' => 'ویرایشگر پیش فرض در زمان ویرایش صفحات را انتخاب نمایید. این انتخاب می تواند جایگزین یک سطح صفحه که مجوز داده شده است، شود.',
'app_custom_html' => 'محتوای اصلی HTML سفارشی',
'app_custom_html_desc' => 'هر محتوای اضافه شده در اینجا در پایین بخش <head> هر صفحه درج می شود. این برای تغییر سبک ها یا اضافه کردن کد تجزیه و تحلیل مفید است.',
'app_custom_html_disabled_notice' => 'محتوای سر HTML سفارشی در این صفحه تنظیمات غیرفعال است تا اطمینان حاصل شود که هر گونه تغییر شکسته می تواند برگردانده شود.',
@ -42,7 +42,7 @@ return [
'app_footer_links' => 'پیوندهای پاورقی',
'app_footer_links_desc' => 'پیوندهایی را برای نمایش در پاورقی سایت اضافه کنید. اینها در پایین اکثر صفحات نمایش داده می شوند، از جمله صفحاتی که نیازی به ورود به سیستم ندارند. می توانید از برچسب "trans::<key>" برای استفاده از ترجمه های تعریف شده توسط سیستم استفاده کنید. به عنوان مثال: با استفاده از "trans::common.privacy_policy" متن ترجمه شده "خط مشی رازداری" و "trans::common.terms_of_service" متن ترجمه شده "شرایط خدمات" را ارائه می دهد.',
'app_footer_links_label' => 'برچسب پیوند',
'app_footer_links_url' => 'لینک URL',
'app_footer_links_url' => 'آدرس پیوند',
'app_footer_links_add' => 'پیوند پاورقی را اضافه کنید',
'app_disable_comments' => 'غیرفعال کردن نظرات',
'app_disable_comments_toggle' => 'نظرات را غیرفعال کنید',
@ -152,7 +152,7 @@ return [
'role_access_api' => 'دسترسی به API سیستم',
'role_manage_settings' => 'تنظیمات برنامه را مدیریت کنید',
'role_export_content' => 'صادرات محتوا',
'role_editor_change' => 'Change page editor',
'role_editor_change' => 'تغییر ویرایشگر صفحه',
'role_asset' => 'مجوزهای دارایی',
'roles_system_warning' => 'توجه داشته باشید که دسترسی به هر یک از سه مجوز فوق می‌تواند به کاربر اجازه دهد تا امتیازات خود یا امتیازات دیگران را در سیستم تغییر دهد. فقط نقش هایی را با این مجوزها به کاربران مورد اعتماد اختصاص دهید.',
'role_asset_desc' => 'این مجوزها دسترسی پیش‌فرض به دارایی‌های درون سیستم را کنترل می‌کنند. مجوزهای مربوط به کتاب‌ها، فصل‌ها و صفحات این مجوزها را لغو می‌کنند.',

View File

@ -23,7 +23,7 @@ return [
'string' => ':attribute باید بین :min و :max کاراکتر باشد.',
'array' => ':attribute باید بین :min و :max آیتم باشد.',
],
'boolean' => 'فیلد :attribute فقط می‌تواند true و یا false باشد.',
'boolean' => ':attribute فقط می‌تواند true و یا false باشد.',
'confirmed' => ':attribute با فیلد تکرار مطابقت ندارد.',
'date' => ':attribute یک تاریخ معتبر نیست.',
'date_format' => ':attribute با الگوی :format مطابقت ندارد.',
@ -31,9 +31,9 @@ return [
'digits' => ':attribute باید :digits رقم باشد.',
'digits_between' => ':attribute باید بین :min و :max رقم باشد.',
'email' => ':attribute باید یک ایمیل معتبر باشد.',
'ends_with' => 'فیلد :attribute باید با یکی از مقادیر زیر خاتمه یابد: :values',
'file' => 'The :attribute must be provided as a valid file.',
'filled' => 'فیلد :attribute باید مقدار داشته باشد.',
'ends_with' => ':attribute باید با یکی از مقادیر زیر خاتمه یابد: :values',
'file' => ':attribute باید به عنوان یک فایل معتبر شناخته شود.',
'filled' => ':attribute باید مقدار داشته باشد.',
'gt' => [
'numeric' => ':attribute باید بزرگتر از :value باشد.',
'file' => ':attribute باید بزرگتر از :value کیلوبایت باشد.',
@ -54,7 +54,7 @@ return [
'ip' => ':attribute باید آدرس IP معتبر باشد.',
'ipv4' => ':attribute باید یک آدرس معتبر از نوع IPv4 باشد.',
'ipv6' => ':attribute باید یک آدرس معتبر از نوع IPv6 باشد.',
'json' => 'فیلد :attribute باید یک رشته از نوع JSON باشد.',
'json' => ':attribute باید یک رشته از نوع JSON باشد.',
'lt' => [
'numeric' => ':attribute باید کوچکتر از :value باشد.',
'file' => ':attribute باید کوچکتر از :value کیلوبایت باشد.',
@ -84,7 +84,7 @@ return [
'not_regex' => 'فرمت :attribute معتبر نیست.',
'numeric' => ':attribute باید عدد یا رشته‌ای از اعداد باشد.',
'regex' => 'فرمت :attribute معتبر نیست.',
'required' => 'فیلد :attribute الزامی است.',
'required' => ':attribute الزامی است.',
'required_if' => 'هنگامی که :other برابر با :value است، فیلد :attribute الزامی است.',
'required_with' => 'در صورت وجود فیلد :values، فیلد :attribute نیز الزامی است.',
'required_with_all' => 'در صورت وجود فیلدهای :values، فیلد :attribute نیز الزامی است.',

View File

@ -157,7 +157,7 @@ return [
'about' => 'エディタについて',
'about_title' => 'WYSIWYGエディタについて',
'editor_license' => 'エディタのライセンスと著作権',
'editor_tiny_license' => 'This editor is built using :tinyLink which is provided under the MIT license.',
'editor_tiny_license' => 'このエディタはMITライセンスの下で提供される:tinyLinkを利用して構築されています。',
'editor_tiny_license_link' => 'TinyMCEの著作権およびライセンスの詳細は、こちらをご覧ください。',
'save_continue' => 'ページを保存して続行',
'callouts_cycle' => '(押し続けて種類を切り替え)',

View File

@ -28,8 +28,8 @@ return [
// Books
'book_create' => '책자 만들기',
'book_create_notification' => '책 생성함',
'book_create_from_chapter' => 'converted chapter to book',
'book_create_from_chapter_notification' => 'Chapter successfully converted to a book',
'book_create_from_chapter' => '챕터를 책으로 변환',
'book_create_from_chapter_notification' => '챕터를 책으로 변환했습니다.',
'book_update' => '책 수정',
'book_update_notification' => '책 수정함',
'book_delete' => '책 지우기',
@ -40,8 +40,8 @@ return [
// Bookshelves
'bookshelf_create' => '책꽂이 만들기',
'bookshelf_create_notification' => '책꽂이 생성함',
'bookshelf_create_from_book' => 'converted book to bookshelf',
'bookshelf_create_from_book_notification' => 'Book successfully converted to a shelf',
'bookshelf_create_from_book' => '책을 책꽂이로 변환',
'bookshelf_create_from_book_notification' => '책을 책꽂이로 변환했습니다.',
'bookshelf_update' => '책꽂이 수정',
'bookshelf_update_notification' => '책꽂이 수정함',
'bookshelf_delete' => '책꽂이 지우기',
@ -64,8 +64,8 @@ return [
'webhook_delete_notification' => '웹 훅 삭제함',
// Users
'user_update_notification' => 'User successfully updated',
'user_delete_notification' => 'User successfully removed',
'user_update_notification' => '사용자가 업데이트되었습니다',
'user_delete_notification' => '사용자가 삭제되었습니다',
// Other
'commented_on' => '댓글 쓰기',

View File

@ -39,9 +39,9 @@ return [
'register_success' => '가입했습니다! 이제 로그인할 수 있습니다.',
// Login auto-initiation
'auto_init_starting' => 'Attempting Login',
'auto_init_starting_desc' => 'We\'re contacting your authentication system to start the login process. If there\'s no progress after 5 seconds you can try clicking the link below.',
'auto_init_start_link' => 'Proceed with authentication',
'auto_init_starting' => '로그인 시도 중',
'auto_init_starting_desc' => '로그인을 시작하기 위해 인증 시스템에 접근 중입니다. 5초 후에도 아무런 반응이 없다면 아래 링크를 클릭하세요.',
'auto_init_start_link' => '인증 진행',
// Password Reset
'reset_password' => '패스워드 바꾸기',

View File

@ -19,7 +19,7 @@ return [
'description' => '설명',
'role' => '권한',
'cover_image' => '대표 이미지',
'cover_image_description' => '이미지 규격은 440x250px 내외입니다.',
'cover_image_description' => '이미지 크기는 440x250px 내외입니다.',
// Actions
'actions' => '활동',
@ -47,8 +47,8 @@ return [
'previous' => '이전',
'filter_active' => '적용 중:',
'filter_clear' => '모든 필터 해제',
'download' => 'Download',
'open_in_tab' => 'Open in Tab',
'download' => '내려받기',
'open_in_tab' => '탭에서 열기',
// Sort Options
'sort_options' => '정렬 기준',

View File

@ -7,58 +7,58 @@
*/
return [
// General editor terms
'general' => 'General',
'advanced' => 'Advanced',
'none' => 'None',
'cancel' => 'Cancel',
'save' => 'Save',
'close' => 'Close',
'undo' => 'Undo',
'redo' => 'Redo',
'left' => 'Left',
'center' => 'Center',
'right' => 'Right',
'top' => 'Top',
'middle' => 'Middle',
'bottom' => 'Bottom',
'width' => 'Width',
'height' => 'Height',
'More' => 'More',
'select' => 'Select...',
'general' => '일반',
'advanced' => '고급',
'none' => '없음',
'cancel' => '취소',
'save' => '저장',
'close' => '닫기',
'undo' => '되돌리기',
'redo' => '다시 실행',
'left' => '왼쪽',
'center' => '가운데',
'right' => '오른쪽',
'top' => '',
'middle' => '가운데',
'bottom' => '아래',
'width' => '너비',
'height' => '높이',
'More' => '더 보기',
'select' => '선택...',
// Toolbar
'formats' => 'Formats',
'header_large' => 'Large Header',
'header_medium' => 'Medium Header',
'header_small' => 'Small Header',
'header_tiny' => 'Tiny Header',
'paragraph' => 'Paragraph',
'blockquote' => 'Blockquote',
'inline_code' => 'Inline code',
'callouts' => 'Callouts',
'callout_information' => 'Information',
'callout_success' => 'Success',
'callout_warning' => 'Warning',
'callout_danger' => 'Danger',
'bold' => 'Bold',
'italic' => 'Italic',
'underline' => 'Underline',
'strikethrough' => 'Strikethrough',
'superscript' => 'Superscript',
'subscript' => 'Subscript',
'text_color' => 'Text color',
'custom_color' => 'Custom color',
'remove_color' => 'Remove color',
'background_color' => 'Background color',
'align_left' => 'Align left',
'align_center' => 'Align center',
'align_right' => 'Align right',
'align_justify' => 'Justify',
'list_bullet' => 'Bullet list',
'list_numbered' => 'Numbered list',
'list_task' => 'Task list',
'indent_increase' => 'Increase indent',
'indent_decrease' => 'Decrease indent',
'formats' => '형식',
'header_large' => '큰 제목',
'header_medium' => '중간 제목',
'header_small' => '작은 제목',
'header_tiny' => '가장 작은 제목',
'paragraph' => '단락',
'blockquote' => '인용',
'inline_code' => '인라인 코드',
'callouts' => '범례',
'callout_information' => '정보',
'callout_success' => '성공',
'callout_warning' => '경고',
'callout_danger' => '위험',
'bold' => '굵게',
'italic' => '기울임',
'underline' => '밑줄',
'strikethrough' => '취소선',
'superscript' => '윗첨자',
'subscript' => '아랫첨자',
'text_color' => '글자 색상',
'custom_color' => '사용자 지정 색상',
'remove_color' => '색상 제거',
'background_color' => '배경 색상',
'align_left' => '왼쪽 정렬',
'align_center' => '가운데 정렬',
'align_right' => '오른쪽 정렬',
'align_justify' => '양쪽 맞춤',
'list_bullet' => '글머리 기호 목록',
'list_numbered' => '번호 매기기 목록',
'list_task' => '작업 목록',
'indent_increase' => '들여쓰기 증가',
'indent_decrease' => '들여쓰기 감소',
'table' => 'Table',
'insert_image' => 'Insert image',
'insert_image_title' => 'Insert/Edit Image',
@ -111,59 +111,59 @@ return [
'paste_row_before' => 'Paste row before',
'paste_row_after' => 'Paste row after',
'row_type' => 'Row type',
'row_type_header' => 'Header',
'row_type_body' => 'Body',
'row_type_footer' => 'Footer',
'alignment' => 'Alignment',
'cut_column' => 'Cut column',
'copy_column' => 'Copy column',
'paste_column_before' => 'Paste column before',
'paste_column_after' => 'Paste column after',
'cell_padding' => 'Cell padding',
'cell_spacing' => 'Cell spacing',
'caption' => 'Caption',
'show_caption' => 'Show caption',
'constrain' => 'Constrain proportions',
'cell_border_solid' => 'Solid',
'cell_border_dotted' => 'Dotted',
'cell_border_dashed' => 'Dashed',
'cell_border_double' => 'Double',
'row_type_header' => '머리글',
'row_type_body' => '본문',
'row_type_footer' => '바닥글',
'alignment' => '정렬',
'cut_column' => '열 잘라내기',
'copy_column' => '열 복사',
'paste_column_before' => '열 앞에 붙여넣기',
'paste_column_after' => '열 뒤에 붙여넣기',
'cell_padding' => '셀 패딩',
'cell_spacing' => '셀 간격',
'caption' => '캡션',
'show_caption' => '캡션 보기',
'constrain' => '비율 유지',
'cell_border_solid' => '단색',
'cell_border_dotted' => '점선',
'cell_border_dashed' => '파선',
'cell_border_double' => '겹선',
'cell_border_groove' => 'Groove',
'cell_border_ridge' => 'Ridge',
'cell_border_inset' => 'Inset',
'cell_border_outset' => 'Outset',
'cell_border_none' => 'None',
'cell_border_hidden' => 'Hidden',
'cell_border_none' => '없음',
'cell_border_hidden' => '숨김',
// Images, links, details/summary & embed
'source' => 'Source',
'alt_desc' => 'Alternative description',
'source' => '원본',
'alt_desc' => '대체 설명',
'embed' => 'Embed',
'paste_embed' => 'Paste your embed code below:',
'url' => 'URL',
'text_to_display' => 'Text to display',
'title' => 'Title',
'open_link' => 'Open link in...',
'open_link_current' => 'Current window',
'open_link_new' => 'New window',
'text_to_display' => '표시할 텍스트',
'title' => '제목',
'open_link' => '링크 열기...',
'open_link_current' => '현재 창',
'open_link_new' => '새 창',
'insert_collapsible' => 'Insert collapsible block',
'collapsible_unwrap' => 'Unwrap',
'edit_label' => 'Edit label',
'toggle_open_closed' => 'Toggle open/closed',
'edit_label' => '레이블 수정',
'toggle_open_closed' => '열림/닫힘 전환',
'collapsible_edit' => 'Edit collapsible block',
'toggle_label' => 'Toggle label',
'toggle_label' => '레이블 보이기/숨기기',
// About view
'about' => 'About the editor',
'about_title' => 'About the WYSIWYG Editor',
'editor_license' => 'Editor License & Copyright',
'about_title' => 'WYSIWYG 편집기에 대하여',
'editor_license' => '편집기 라이선스 & 저작권',
'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',
'save_continue' => '저장하고 계속하기',
'callouts_cycle' => '(Keep pressing to toggle through types)',
'link_selector' => 'Link to content',
'shortcuts' => 'Shortcuts',
'shortcut' => 'Shortcut',
'shortcuts' => '단축키',
'shortcut' => '단축키',
'shortcuts_intro' => 'The following shortcuts are available in the editor:',
'windows_linux' => '(Windows/Linux)',
'mac' => '(Mac)',

View File

@ -24,7 +24,7 @@ return [
'meta_updated_name' => '수정함 :timeLength, :user',
'meta_owned_name' => '소유함 :user',
'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' => '내가 읽은 문서',
@ -88,7 +88,7 @@ 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' => ':name 바꾸기',
@ -171,7 +171,7 @@ return [
'chapters_permissions_active' => '문서 권한 허용함',
'chapters_permissions_success' => '권한 저장함',
'chapters_search_this' => '이 챕터에서 검색',
'chapter_sort_book' => 'Sort Book',
'chapter_sort_book' => '책 정렬하기',
// Pages
'page' => '문서',
@ -198,16 +198,16 @@ return [
'pages_edit_draft_save_at' => '보관함: ',
'pages_edit_delete_draft' => '초안 삭제',
'pages_edit_discard_draft' => '폐기',
'pages_edit_switch_to_markdown' => 'Switch to Markdown Editor',
'pages_edit_switch_to_markdown' => '마크다운 편집기로 전환',
'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_wysiwyg' => 'WYSIWYG 편집기로 전환',
'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_title' => '편집기 전환',
'pages_editor_switch_are_you_sure' => '이 페이지의 편집기를 변경하시겠어요?',
'pages_editor_switch_consider_following' => '편집기를 전환할 때에 다음 사항들을 고려하세요:',
'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.',
@ -237,7 +237,7 @@ return [
'pages_revisions_number' => 'No.',
'pages_revisions_numbered' => '수정본 :id',
'pages_revisions_numbered_changes' => '수정본 :id에서 바꾼 부분',
'pages_revisions_editor' => 'Editor Type',
'pages_revisions_editor' => '편집기 유형',
'pages_revisions_changelog' => '설명',
'pages_revisions_changes' => '바꾼 부분',
'pages_revisions_current' => '현재 판본',
@ -359,14 +359,14 @@ return [
'copy_consider_access' => '경로, 소유자, 권한이 바뀌면 이 문서를 본 적 없는 사용자가 볼 수도 있습니다.',
// Conversions
'convert_to_shelf' => '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_book' => '책 변환',
'convert_book_confirm' => '이 책을 변환하시겠어요?',
'convert_undo_warning' => '이 작업은 되돌리기 어렵습니다.',
'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_chapter' => '챕터 변환',
'convert_chapter_confirm' => '이 챕터를 변환하시겠어요?',
];

View File

@ -87,7 +87,7 @@ return [
'404_page_not_found' => '404 Not Found',
'sorry_page_not_found' => '문서를 못 찾았습니다.',
'sorry_page_not_found_permission_warning' => '문서를 볼 권한이 없습니다.',
'image_not_found' => 'Image Not Found',
'image_not_found' => '이미지를 찾을 수 없습니다',
'image_not_found_subtitle' => '이미지를 못 찾았습니다.',
'image_not_found_details' => '이미지가 지워졌을 수 있습니다.',
'return_home' => '처음으로 돌아가기',

View File

@ -10,8 +10,8 @@ return [
'settings' => '설정',
'settings_save' => '적용',
'settings_save_success' => '설정 적용함',
'system_version' => 'System Version',
'categories' => 'Categories',
'system_version' => '시스템 버전',
'categories' => '카테고리',
// App Settings
'app_customization' => '맞춤',
@ -27,8 +27,8 @@ return [
'app_secure_images' => '이미지 주소 보호',
'app_secure_images_toggle' => '이미지 주소 보호',
'app_secure_images_desc' => '성능상의 문제로 이미지에 누구나 접근할 수 있기 때문에 이미지 주소를 무작위한 문자로 구성합니다. 폴더 색인을 끄세요.',
'app_default_editor' => 'Default Page Editor',
'app_default_editor_desc' => 'Select which editor will be used by default when editing new pages. This can be overridden at a page level where permissions allow.',
'app_default_editor' => '기본 페이지 편집기',
'app_default_editor_desc' => '새 페이지를 편집할 때 기본으로 사용될 편집기를 선택합니다. 권한을 갖고 있다면 페이지마다 다르게 적용될 수 있습니다.',
'app_custom_html' => '헤드 작성',
'app_custom_html_desc' => '설정 페이지를 제외한 모든 페이지 head 태그 끝머리에 추가합니다.',
'app_custom_html_disabled_notice' => '문제가 생겨도 설정 페이지에서 되돌릴 수 있어요.',
@ -152,7 +152,7 @@ return [
'role_access_api' => '시스템 접근 API',
'role_manage_settings' => '사이트 설정 관리',
'role_export_content' => '항목 내보내기',
'role_editor_change' => 'Change page editor',
'role_editor_change' => '페이지 편집기 변경',
'role_asset' => '권한 항목',
'roles_system_warning' => '위 세 권한은 자신의 권한이나 다른 유저의 권한을 바꿀 수 있습니다.',
'role_asset_desc' => '책, 챕터, 문서별 권한은 이 설정에 우선합니다.',

View File

@ -16,6 +16,7 @@
<div class="mb-xs"><a href="#listing-endpoints">Listing Endpoints</a></div>
<div class="mb-xs"><a href="#error-handling">Error Handling</a></div>
<div class="mb-xs"><a href="#rate-limits">Rate Limits</a></div>
<div class="mb-xs"><a href="#content-security">Content Security</a></div>
</div>
@foreach($docs as $model => $endpoints)

View File

@ -179,4 +179,20 @@ API_REQUESTS_PER_MIN=180</code></pre>
It's generally good practice to limit requests made from your API client, where possible, to avoid
affecting normal use of the system caused by over-consuming system resources.
Keep in mind there may be other rate-limiting factors such as web-server & firewall controls.
</p>
<hr>
<h5 id="content-security" class="text-mono mb-m">Content Security</h5>
<p>
Many of the available endpoints will return content that has been provided by user input.
Some of this content may be provided in a certain data-format (Such as HTML or Markdown for page content).
Such content is not guaranteed to be safe so keep security in mind when dealing with such user-input.
In some cases, the system will apply some filtering to content in an attempt to prevent certain vulnerabilities, but
this is not assured to be a bullet-proof defence.
</p>
<p>
Within its own interfaces, unless disabled, the system makes use of Content Security Policy (CSP) rules to heavily negate
cross-site scripting vulnerabilities from user content. If displaying user content externally, it's advised you
also use defences such as CSP or the disabling of JavaScript completely.
</p>

View File

@ -0,0 +1,69 @@
<tr>
<td>{{ $revision->revision_number == 0 ? '' : $revision->revision_number }}</td>
<td>
{{ $revision->name }}
<br>
<small class="text-muted">({{ $revision->is_markdown ? 'Markdown' : 'WYSIWYG' }})</small>
</td>
<td style="line-height: 0;" width="30">
@if($revision->createdBy)
<img class="avatar" src="{{ $revision->createdBy->getAvatar(30) }}" alt="{{ $revision->createdBy->name }}">
@endif
</td>
<td width="260">
@if($revision->createdBy) {{ $revision->createdBy->name }} @else {{ trans('common.deleted_user') }} @endif
<br>
<div class="text-muted">
<small>{{ $revision->created_at->formatLocalized('%e %B %Y %H:%M:%S') }}</small>
<small>({{ $revision->created_at->diffForHumans() }})</small>
</div>
</td>
<td>
{{ $revision->summary }}
</td>
<td class="actions text-small text-right">
<a href="{{ $revision->getUrl('changes') }}" target="_blank" rel="noopener">{{ trans('entities.pages_revisions_changes') }}</a>
<span class="text-muted">&nbsp;|&nbsp;</span>
@if ($index === 0)
<a target="_blank" rel="noopener" href="{{ $revision->page->getUrl() }}"><i>{{ trans('entities.pages_revisions_current') }}</i></a>
@else
<a href="{{ $revision->getUrl() }}" target="_blank" rel="noopener">{{ trans('entities.pages_revisions_preview') }}</a>
<span class="text-muted">&nbsp;|&nbsp;</span>
<div component="dropdown" class="dropdown-container">
<a refs="dropdown@toggle" href="#" aria-haspopup="true" aria-expanded="false">{{ trans('entities.pages_revisions_restore') }}</a>
<ul refs="dropdown@menu" class="dropdown-menu" role="menu">
<li class="px-m py-s"><small class="text-muted">{{trans('entities.revision_restore_confirm')}}</small></li>
<li>
<form action="{{ $revision->getUrl('/restore') }}" method="POST">
{!! csrf_field() !!}
<input type="hidden" name="_method" value="PUT">
<button type="submit" class="text-primary icon-item">
@icon('history')
<div>{{ trans('entities.pages_revisions_restore') }}</div>
</button>
</form>
</li>
</ul>
</div>
<span class="text-muted">&nbsp;|&nbsp;</span>
<div component="dropdown" class="dropdown-container">
<a refs="dropdown@toggle" href="#" aria-haspopup="true" aria-expanded="false">{{ trans('common.delete') }}</a>
<ul refs="dropdown@menu" class="dropdown-menu" role="menu">
<li class="px-m py-s"><small class="text-muted">{{trans('entities.revision_delete_confirm')}}</small></li>
<li>
<form action="{{ $revision->getUrl('/delete/') }}" method="POST">
{!! csrf_field() !!}
<input type="hidden" name="_method" value="DELETE">
<button type="submit" class="text-neg icon-item">
@icon('delete')
<div>{{ trans('common.delete') }}</div>
</button>
</form>
</li>
</ul>
</div>
@endif
</td>
</tr>

View File

@ -17,11 +17,11 @@
<main class="card content-wrap">
<h1 class="list-heading">{{ trans('entities.pages_revisions') }}</h1>
@if(count($page->revisions) > 0)
@if(count($revisions) > 0)
<table class="table">
<tr>
<th width="40">{{ trans('entities.pages_revisions_number') }}</th>
<th width="56">{{ trans('entities.pages_revisions_number') }}</th>
<th>
{{ trans('entities.pages_name') }} / {{ trans('entities.pages_revisions_editor') }}
</th>
@ -29,76 +29,8 @@
<th>{{ trans('entities.pages_revisions_changelog') }}</th>
<th class="text-right">{{ trans('common.actions') }}</th>
</tr>
@foreach($page->revisions as $index => $revision)
<tr>
<td>{{ $revision->revision_number == 0 ? '' : $revision->revision_number }}</td>
<td>
{{ $revision->name }}
<br>
<small class="text-muted">({{ $revision->markdown ? 'Markdown' : 'WYSIWYG' }})</small>
</td>
<td style="line-height: 0;" width="30">
@if($revision->createdBy)
<img class="avatar" src="{{ $revision->createdBy->getAvatar(30) }}" alt="{{ $revision->createdBy->name }}">
@endif
</td>
<td width="260">
@if($revision->createdBy) {{ $revision->createdBy->name }} @else {{ trans('common.deleted_user') }} @endif
<br>
<div class="text-muted">
<small>{{ $revision->created_at->formatLocalized('%e %B %Y %H:%M:%S') }}</small>
<small>({{ $revision->created_at->diffForHumans() }})</small>
</div>
</td>
<td>
{{ $revision->summary }}
</td>
<td class="actions text-small text-right">
<a href="{{ $revision->getUrl('changes') }}" target="_blank" rel="noopener">{{ trans('entities.pages_revisions_changes') }}</a>
<span class="text-muted">&nbsp;|&nbsp;</span>
@if ($index === 0)
<a target="_blank" rel="noopener" href="{{ $page->getUrl() }}"><i>{{ trans('entities.pages_revisions_current') }}</i></a>
@else
<a href="{{ $revision->getUrl() }}" target="_blank" rel="noopener">{{ trans('entities.pages_revisions_preview') }}</a>
<span class="text-muted">&nbsp;|&nbsp;</span>
<div component="dropdown" class="dropdown-container">
<a refs="dropdown@toggle" href="#" aria-haspopup="true" aria-expanded="false">{{ trans('entities.pages_revisions_restore') }}</a>
<ul refs="dropdown@menu" class="dropdown-menu" role="menu">
<li class="px-m py-s"><small class="text-muted">{{trans('entities.revision_restore_confirm')}}</small></li>
<li>
<form action="{{ $revision->getUrl('/restore') }}" method="POST">
{!! csrf_field() !!}
<input type="hidden" name="_method" value="PUT">
<button type="submit" class="text-primary icon-item">
@icon('history')
<div>{{ trans('entities.pages_revisions_restore') }}</div>
</button>
</form>
</li>
</ul>
</div>
<span class="text-muted">&nbsp;|&nbsp;</span>
<div component="dropdown" class="dropdown-container">
<a refs="dropdown@toggle" href="#" aria-haspopup="true" aria-expanded="false">{{ trans('common.delete') }}</a>
<ul refs="dropdown@menu" class="dropdown-menu" role="menu">
<li class="px-m py-s"><small class="text-muted">{{trans('entities.revision_delete_confirm')}}</small></li>
<li>
<form action="{{ $revision->getUrl('/delete/') }}" method="POST">
{!! csrf_field() !!}
<input type="hidden" name="_method" value="DELETE">
<button type="submit" class="text-neg icon-item">
@icon('delete')
<div>{{ trans('common.delete') }}</div>
</button>
</form>
</li>
</ul>
</div>
@endif
</td>
</tr>
@foreach($revisions as $index => $revision)
@include('pages.parts.revision-table-row', ['revision' => $revision])
@endforeach
</table>

View File

@ -325,11 +325,14 @@ class PageContentTest extends TestCase
$pageView->assertDontSee('abc123abc123');
}
public function test_svg_xlink_hrefs_are_removed()
public function test_svg_script_usage_is_removed()
{
$checks = [
'<svg id="test" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100" height="100"><a xlink:href="javascript:alert(document.domain)"><rect x="0" y="0" width="100" height="100" /></a></svg>',
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><use xlink:href="data:application/xml;base64 ,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj4KPGRlZnM+CjxjaXJjbGUgaWQ9InRlc3QiIHI9IjAiIGN4PSIwIiBjeT0iMCIgc3R5bGU9ImZpbGw6ICNGMDAiPgo8c2V0IGF0dHJpYnV0ZU5hbWU9ImZpbGwiIGF0dHJpYnV0ZVR5cGU9IkNTUyIgb25iZWdpbj0nYWxlcnQoZG9jdW1lbnQuZG9tYWluKScKb25lbmQ9J2FsZXJ0KCJvbmVuZCIpJyB0bz0iIzAwRiIgYmVnaW49IjBzIiBkdXI9Ijk5OXMiIC8+CjwvY2lyY2xlPgo8L2RlZnM+Cjx1c2UgeGxpbms6aHJlZj0iI3Rlc3QiLz4KPC9zdmc+#test"/></svg>',
'<svg><animate href=#xss attributeName=href values=javascript:alert(1) /></svg>',
'<svg><animate href="#xss" attributeName="href" values="a;javascript:alert(1)" /></svg>',
'<svg><animate href="#xss" attributeName="href" values="a;data:alert(1)" /></svg>',
];
$this->asEditor();
@ -341,9 +344,11 @@ class PageContentTest extends TestCase
$pageView = $this->get($page->getUrl());
$pageView->assertStatus(200);
$this->withHtml($pageView)->assertElementNotContains('.page-content', 'alert');
$this->withHtml($pageView)->assertElementNotContains('.page-content', 'xlink:href');
$this->withHtml($pageView)->assertElementNotContains('.page-content', 'application/xml');
$html = $this->withHtml($pageView);
$html->assertElementNotContains('.page-content', 'alert');
$html->assertElementNotContains('.page-content', 'xlink:href');
$html->assertElementNotContains('.page-content', 'application/xml');
$html->assertElementNotContains('.page-content', 'javascript');
}
}

View File

@ -1,11 +1,11 @@
<?php
<?php namespace Tests;
use BookStack\Actions\Favourite;
use BookStack\Auth\User;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Bookshelf;
use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Page;
use Tests\TestCase;
class FavouriteTest extends TestCase
{
@ -58,6 +58,30 @@ class FavouriteTest extends TestCase
]);
}
public function test_favourite_flow_with_own_permissions()
{
/** @var Book $book */
$book = Book::query()->first();
$user = User::factory()->create();
$book->owned_by = $user->id;
$book->save();
$this->giveUserPermissions($user, ['book-view-own']);
$this->actingAs($user)->get($book->getUrl());
$resp = $this->post('/favourites/add', [
'type' => get_class($book),
'id' => $book->id,
]);
$resp->assertRedirect($book->getUrl());
$this->assertDatabaseHas('favourites', [
'user_id' => $user->id,
'favouritable_type' => $book->getMorphClass(),
'favouritable_id' => $book->id,
]);
}
public function test_book_chapter_shelf_pages_contain_favourite_button()
{
$entities = [