Compare commits

..

59 Commits

Author SHA1 Message Date
Dan Brown
934512d09c Updated version and assets for release v0.25.5 2019-03-24 19:45:17 +00:00
Dan Brown
9102c90986 Merge branch 'master' into release 2019-03-24 19:45:00 +00:00
Dan Brown
9879a0d12c Added helper text for no_double_extension validation 2019-03-24 19:40:45 +00:00
Dan Brown
5d2e80bff3 Merge pull request #1348 from agvol/master
Russian translation
2019-03-24 19:14:53 +00:00
Dan Brown
83818234c8 Merge pull request #1347 from cima/czech-translation
Czech translation
2019-03-24 19:13:48 +00:00
Dan Brown
5c00187138 Merge pull request #1327 from leomartinez/master
Updated 'Spanish Argentina' translation.
2019-03-24 19:11:01 +00:00
Dan Brown
193e2ffebe Prevent dbl exts. on img upload, Randomized attachment upload names 2019-03-24 19:08:21 +00:00
agvol
b2a5d07787 Update russian lang 2019-03-23 10:22:54 +03:00
agvol
15cf9f8b32 Update russian lang 2019-03-23 10:18:44 +03:00
agvol
f9adfee47a Update russian lang 2019-03-23 10:04:50 +03:00
Martin Šimek
2e988277f0 Czech translation
+ Unit test repair
2019-03-23 00:35:42 +01:00
Martin Šimek
4e8e28c35f Czech translation
+ Typos
+ errors.php
2019-03-22 22:52:26 +01:00
Dan Brown
c3e74219c4 Updated version and assets for release v0.25.4 2019-03-21 19:46:19 +00:00
Dan Brown
13c9d7bc2d Merge branch 'master' into release 2019-03-21 19:43:48 +00:00
Dan Brown
f5fe524e6c Added extension whitelist for image uploads
- A continuation of the security issues addressed in v0.25.3
2019-03-21 19:43:15 +00:00
Dan Brown
119b539586 Updated version and assets for release v0.25.3 2019-03-21 00:03:26 +00:00
Dan Brown
29a5c180f0 Merge branch 'master' into release 2019-03-21 00:02:33 +00:00
Dan Brown
37b91b6b0e Hardened image file validation by removing custom validation
- Added test to check PHP files cannot be uploaded as an image.
2019-03-20 23:59:55 +00:00
Martin Šimek
b3a4d8af2a Czech translation
+ Czech language (cs)
+ settings.php in english populated by new option
Note: validation php taken from  lavarel official translation (https://github.com/caouecs/Laravel-lang/blob/master/src/cs/validation.php)
2019-03-19 22:45:14 +01:00
Leonardo Martinez
64cd499542 Updated 'Spanish Argentina' translation. 2019-03-12 11:46:02 -03:00
Dan Brown
7906602291 Updated version and assets for release v0.25.2 2019-03-10 13:45:21 +00:00
Dan Brown
6dafe773ff Merge branch 'master' into release 2019-03-10 13:44:29 +00:00
Dan Brown
00703fa817 Merge branch 'dfanara-feature-ldap-attributes' 2019-03-10 10:55:36 +00:00
Dan Brown
44c537de1a Performed some LDAP service/test cleanup 2019-03-10 10:54:19 +00:00
Dan Brown
6bccf0e64a Merge branch 'feature-ldap-attributes' of git://github.com/dfanara/BookStack into dfanara-feature-ldap-attributes 2019-03-10 10:31:09 +00:00
Dan Brown
042a6f9760 Updated shelf menu item to show on custom permission
- Extended new 'userCanOnAny' helper to take a entity class for
filtering.

Closes #1201
2019-03-09 21:15:45 +00:00
Dan Brown
04287745e4 Merge branch 'mark-james-Copy-For-View-Only' 2019-03-09 16:52:47 +00:00
Dan Brown
5c9b528517 Abstracted userCanCreatePage helper to work for any permisison
- Added test to cover scenario where someone with create-own permission
would want to copy a viewable item into a container entity that they
own.
2019-03-09 16:50:22 +00:00
Dan Brown
6be2d3f28c Merge branch 'Copy-For-View-Only' of git://github.com/mark-james/BookStack into mark-james-Copy-For-View-Only 2019-03-09 16:12:12 +00:00
Daniel Fanara
6d20bdc1fb Preserve original display_name_attribute configuration values. 2019-03-09 01:13:30 -05:00
Daniel Fanara
502ea608bf Issue #1306 - Unit Tests for LdapService Changes 2019-03-09 01:08:49 -05:00
Daniel Fanara
55b07c7076 Issue #1306 - Specify display name attribute from LDAP 2019-03-08 23:55:11 -05:00
Dan Brown
33e999909f Merge branch 'fix-1186' 2019-03-08 22:57:57 +00:00
Dan Brown
6be95cd2ac Re-centered dropzone error arrow 2019-03-08 22:57:24 +00:00
Dan Brown
f467185e86 Merge branch 'master' into fix-1186 2019-03-08 22:47:31 +00:00
Dan Brown
646fd822c5 Updated redis config logic, Now takes a password
- Previous config did not use multiple servers in any way.
- Cluster will now be created automatically if multiple servers given.
- Removed REDIS_CLUSTER option.

Closes #1283
2019-03-08 22:42:48 +00:00
Dan Brown
d96baf2d4a Set 'uploaded_to' parameters for editor-pasted/dragged images
Allows image-listing permission system to work as intended.
Fixes #1287
2019-03-08 21:32:31 +00:00
Dan Brown
1c312906bc Added a configurable upload size limit
Closes #1293
2019-03-08 21:06:37 +00:00
Dan Brown
9126c87f2b Merge pull request #1272 from Xiphoseer/patch-1
Add german translations for shelves
2019-03-07 22:14:35 +00:00
Dan Brown
579d98a908 Merge pull request #1314 from maantje/patch-2
Update Dutch password_hint translation to correspond with validation
2019-03-07 21:53:08 +00:00
Dan Brown
98a4359198 Updated user language select to use correct default
- Updated localisation system to take note of system defaul locale
before replacing the current locale
Fixes #1316
2019-03-07 21:09:23 +00:00
Jamie Schouten
6f710225b5 Update Dutch password_hint translation to correspond with validation rule
At the moment the translation says ```Minimaal 5 tekens``` which means your password should be at least 5 characters long. But a 5 character long password is not allowed by the validator. 

I think this was a translation error from the English one where it says ```Must be over 5 characters```. To make the Dutch translation correct the Dutch translation should be changed to ```Minimaal 6 tekens```.

```
    /**
     * Get a validator for an incoming registration request.
     *
     * @param  array $data
     * @return \Illuminate\Contracts\Validation\Validator
     */
    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => 'required|max:255',
            'email' => 'required|email|max:255|unique:users',
            'password' => 'required|min:6',
        ]);
    }
```
2019-03-06 17:10:15 +01:00
Dan Brown
b273b9d6d0 Improved alignment classes used by WYSIWYG editor
- Fixed table cells being floated, Fixes #1284.
- Made it possible to easily center linked images.
2019-03-02 09:08:01 +00:00
Dan Brown
e471d0c52a Added lua to code languages
Closes #1223
2019-03-02 08:52:14 +00:00
Dan Brown
0a431e3223 Merge pull request #1263 from christophert/add-powershellmarkup
Add Powershell Code Markup
2019-03-02 08:45:08 +00:00
Xiphoseer
058cc2cbd6 Update entities.php 2019-02-12 12:30:43 +01:00
Xiphoseer
edd98c00e5 Update entities.php 2019-02-12 12:30:12 +01:00
Xiphoseer
19bb11a1c9 Update entities.php
Add informal german shelve localisations
2019-02-12 12:28:48 +01:00
Xiphoseer
9511d10ec8 Update entities.php
Add german shelve localizations
2019-02-12 12:24:01 +01:00
Christopher Tran
77d3bd31a6 add powershell code block link 2019-02-06 01:06:59 -05:00
Dan Brown
56004abdf4 Added high-level release and roadmap info to readme
Closes #1259
2019-02-06 00:09:39 +00:00
Dan Brown
df6f6e2d77 Merge branch 'master' of github.com:BookStackApp/BookStack 2019-02-04 19:57:43 +00:00
Dan Brown
ba1b3fc181 Made some readme tweaks 2019-02-04 19:57:21 +00:00
abijeet
9dba9ca178 Fixes tooltip on the image manager.
Fixes #1186
2019-01-27 19:43:31 +05:30
Abijeet Patro
8a4a81629f Merge pull request #1237 from BookStackApp/phpcs-fixes
PHPCS related fixes.
2019-01-27 16:04:30 +05:30
abijeet
5ef0992d5b PHPCS related fixes. 2019-01-27 15:59:23 +05:30
Mark James
19770d2792 Use joint_permissions to determine is a user has an available page or chapter to copy. 2019-01-02 16:55:28 +11:00
Mark James
99c6d70c51 Initial updates to allow for page copy when the user can read the page but can't update it. 2018-12-31 17:01:49 +11:00
mark-james
0830521e60 Merge pull request #1 from BookStackApp/master
Update From Bookstack
2018-12-31 09:00:19 +11:00
64 changed files with 1467 additions and 193 deletions

View File

@@ -75,6 +75,12 @@ CACHE_PREFIX=bookstack
# For multiple servers separate with a comma
MEMCACHED_SERVERS=127.0.0.1:11211:100
# Redis server configuration
# This follows the following format: HOST:PORT:DATABASE
# or, if using a password: HOST:PORT:DATABASE:PASSWORD
# For multiple servers separate with a comma. These will be clustered.
REDIS_SERVERS=127.0.0.1:6379:0
# Queue driver to use
# Queue not really currently used but may be configurable in the future.
# Would advise not to change this for now.
@@ -171,6 +177,7 @@ LDAP_USER_FILTER=false
LDAP_VERSION=false
LDAP_TLS_INSECURE=false
LDAP_EMAIL_ATTRIBUTE=mail
LDAP_DISPLAY_NAME_ATTRIBUTE=cn
LDAP_FOLLOW_REFERRALS=true
# LDAP group sync configuration

View File

@@ -80,20 +80,40 @@ class LdapService
public function getUserDetails($userName)
{
$emailAttr = $this->config['email_attribute'];
$user = $this->getUserWithAttributes($userName, ['cn', 'uid', 'dn', $emailAttr]);
$displayNameAttr = $this->config['display_name_attribute'];
$user = $this->getUserWithAttributes($userName, ['cn', 'uid', 'dn', $emailAttr, $displayNameAttr]);
if ($user === null) {
return null;
}
$userCn = $this->getUserResponseProperty($user, 'cn', null);
return [
'uid' => (isset($user['uid'])) ? $user['uid'][0] : $user['dn'],
'name' => $user['cn'][0],
'uid' => $this->getUserResponseProperty($user, 'uid', $user['dn']),
'name' => $this->getUserResponseProperty($user, $displayNameAttr, $userCn),
'dn' => $user['dn'],
'email' => (isset($user[$emailAttr])) ? (is_array($user[$emailAttr]) ? $user[$emailAttr][0] : $user[$emailAttr]) : null
'email' => $this->getUserResponseProperty($user, $emailAttr, null),
];
}
/**
* Get a property from an LDAP user response fetch.
* Handles properties potentially being part of an array.
* @param array $userDetails
* @param string $propertyKey
* @param $defaultValue
* @return mixed
*/
protected function getUserResponseProperty(array $userDetails, string $propertyKey, $defaultValue)
{
if (isset($userDetails[$propertyKey])) {
return (is_array($userDetails[$propertyKey]) ? $userDetails[$propertyKey][0] : $userDetails[$propertyKey]);
}
return $defaultValue;
}
/**
* @param Authenticatable $user
* @param string $username
@@ -176,8 +196,8 @@ class LdapService
* the LDAP_OPT_X_TLS_REQUIRE_CERT option. It can only be set globally and not
* per handle.
*/
if($this->config['tls_insecure']) {
$this->ldap->setOption(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_NEVER);
if ($this->config['tls_insecure']) {
$this->ldap->setOption(null, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_NEVER);
}
$ldapConnection = $this->ldap->connect($hostName, count($ldapServer) > 2 ? intval($ldapServer[2]) : $defaultPort);

View File

@@ -190,10 +190,10 @@ class PermissionService
{
return $this->entityProvider->book->newQuery()
->select(['id', 'restricted', 'created_by'])->with(['chapters' => function ($query) {
$query->select(['id', 'restricted', 'created_by', 'book_id']);
}, 'pages' => function ($query) {
$query->select(['id', 'restricted', 'created_by', 'book_id', 'chapter_id']);
}]);
$query->select(['id', 'restricted', 'created_by', 'book_id']);
}, 'pages' => function ($query) {
$query->select(['id', 'restricted', 'created_by', 'book_id', 'chapter_id']);
}]);
}
/**
@@ -556,6 +556,39 @@ class PermissionService
return $q;
}
/**
* Checks if a user has the given permission for any items in the system.
* Can be passed an entity instance to filter on a specific type.
* @param string $permission
* @param string $entityClass
* @return bool
*/
public function checkUserHasPermissionOnAnything(string $permission, string $entityClass = null)
{
$userRoleIds = $this->currentUser()->roles()->select('id')->pluck('id')->toArray();
$userId = $this->currentUser()->id;
$permissionQuery = $this->db->table('joint_permissions')
->where('action', '=', $permission)
->whereIn('role_id', $userRoleIds)
->where(function ($query) use ($userId) {
$query->where('has_permission', '=', 1)
->orWhere(function ($query2) use ($userId) {
$query2->where('has_permission_own', '=', 1)
->where('created_by', '=', $userId);
});
}) ;
if (!is_null($entityClass)) {
$entityInstance = app()->make($entityClass);
$permissionQuery = $permissionQuery->where('entity_type', '=', $entityInstance->getMorphClass());
}
$hasPermission = $permissionQuery->count() > 0;
$this->clean();
return $hasPermission;
}
/**
* Check if an entity has restrictions set on itself or its
* parent tree.
@@ -612,13 +645,13 @@ class PermissionService
$entities = $this->entityProvider;
$pageSelect = $this->db->table('pages')->selectRaw($entities->page->entityRawQuery($fetchPageContent))
->where('book_id', '=', $book_id)->where(function ($query) use ($filterDrafts) {
$query->where('draft', '=', 0);
if (!$filterDrafts) {
$query->orWhere(function ($query) {
$query->where('draft', '=', 1)->where('created_by', '=', $this->currentUser()->id);
});
}
});
$query->where('draft', '=', 0);
if (!$filterDrafts) {
$query->orWhere(function ($query) {
$query->where('draft', '=', 1)->where('created_by', '=', $this->currentUser()->id);
});
}
});
$chapterSelect = $this->db->table('chapters')->selectRaw($entities->chapter->entityRawQuery())->where('book_id', '=', $book_id);
$query = $this->db->query()->select('*')->from($this->db->raw("({$pageSelect->toSql()} UNION {$chapterSelect->toSql()}) AS U"))
->mergeBindings($pageSelect)->mergeBindings($chapterSelect);

View File

@@ -84,6 +84,4 @@ class EntityProvider
$type = strtolower($type);
return $this->all()[$type];
}
}
}

View File

@@ -505,4 +505,4 @@ class PageRepo extends EntityRepo
return $this->publishPageDraft($copyPage, $pageData);
}
}
}

View File

@@ -2,4 +2,6 @@
use Exception;
class HttpFetchException extends Exception {}
class HttpFetchException extends Exception
{
}

View File

@@ -1,3 +1,5 @@
<?php namespace BookStack\Exceptions;
class UserUpdateException extends NotifyException {}
class UserUpdateException extends NotifyException
{
}

View File

@@ -119,7 +119,7 @@ class ImageController extends Controller
{
$this->checkPermission('image-create-all');
$this->validate($request, [
'file' => 'is_image'
'file' => 'image_extension|no_double_extension|mimes:jpeg,png,gif,bmp,webp,tiff'
]);
if (!$this->imageRepo->isValidType($type)) {
@@ -135,7 +135,6 @@ class ImageController extends Controller
return response($e->getMessage(), 500);
}
return response()->json($image);
}

View File

@@ -643,7 +643,7 @@ class PageController extends Controller
public function showCopy($bookSlug, $pageSlug)
{
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
$this->checkOwnablePermission('page-update', $page);
$this->checkOwnablePermission('page-view', $page);
session()->flashInput(['name' => $page->name]);
return view('pages/copy', [
'book' => $page->book,
@@ -662,7 +662,7 @@ class PageController extends Controller
public function copy($bookSlug, $pageSlug, Request $request)
{
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
$this->checkOwnablePermission('page-update', $page);
$this->checkOwnablePermission('page-view', $page);
$entitySelection = $request->get('entity_selection', null);
if ($entitySelection === null || $entitySelection === '') {

View File

@@ -51,6 +51,7 @@ class Localization
public function handle($request, Closure $next)
{
$defaultLang = config('app.locale');
config()->set('app.default_locale', $defaultLang);
if (user()->isDefault() && config('app.auto_detect_locale')) {
$locale = $this->autoDetectLocale($request, $defaultLang);
@@ -63,8 +64,6 @@ class Localization
config()->set('app.rtl', true);
}
app()->setLocale($locale);
Carbon::setLocale($locale);
$this->setSystemDateLocale($locale);

View File

@@ -31,5 +31,4 @@ class MailNotification extends Notification implements ShouldQueue
'text' => 'vendor.notifications.email-plain'
]);
}
}
}

View File

@@ -1,6 +1,5 @@
<?php namespace BookStack\Notifications;
class ResetPassword extends MailNotification
{
/**

View File

@@ -8,6 +8,7 @@ use BookStack\Entities\Page;
use BookStack\Settings\Setting;
use BookStack\Settings\SettingService;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\ServiceProvider;
use Schema;
use Validator;
@@ -22,11 +23,17 @@ class AppServiceProvider extends ServiceProvider
public function boot()
{
// Custom validation methods
Validator::extend('is_image', function ($attribute, $value, $parameters, $validator) {
$imageMimes = ['image/png', 'image/bmp', 'image/gif', 'image/jpeg', 'image/jpg', 'image/tiff', 'image/webp'];
return in_array($value->getMimeType(), $imageMimes);
Validator::extend('image_extension', function ($attribute, $value, $parameters, $validator) {
$validImageExtensions = ['png', 'jpg', 'jpeg', 'bmp', 'gif', 'tiff', 'webp'];
return in_array(strtolower($value->getClientOriginalExtension()), $validImageExtensions);
});
Validator::extend('no_double_extension', function ($attribute, $value, $parameters, $validator) {
$uploadName = $value->getClientOriginalName();
return substr_count($uploadName, '.') < 2;
});
// Custom blade view directives
Blade::directive('icon', function ($expression) {
return "<?php echo icon($expression); ?>";

View File

@@ -1,6 +1,5 @@
<?php namespace BookStack\Providers;
use BookStack\Translation\Translator;
class TranslationServiceProvider extends \Illuminate\Translation\TranslationServiceProvider
@@ -29,4 +28,4 @@ class TranslationServiceProvider extends \Illuminate\Translation\TranslationServ
return $trans;
});
}
}
}

View File

@@ -41,6 +41,7 @@ class SettingService
if ($default === false) {
$default = config('setting-defaults.' . $key, false);
}
if (isset($this->localCache[$key])) {
return $this->localCache[$key];
}

View File

@@ -1,6 +1,5 @@
<?php namespace BookStack\Translation;
class Translator extends \Illuminate\Translation\Translator
{
@@ -70,5 +69,4 @@ class Translator extends \Illuminate\Translation\Translator
{
return $this->baseLocaleMap[$locale] ?? null;
}
}
}

View File

@@ -44,7 +44,7 @@ class AttachmentService extends UploadService
public function saveNewUpload(UploadedFile $uploadedFile, $page_id)
{
$attachmentName = $uploadedFile->getClientOriginalName();
$attachmentPath = $this->putFileInStorage($attachmentName, $uploadedFile);
$attachmentPath = $this->putFileInStorage($uploadedFile);
$largestExistingOrder = Attachment::where('uploaded_to', '=', $page_id)->max('order');
$attachment = Attachment::forceCreate([
@@ -75,7 +75,7 @@ class AttachmentService extends UploadService
}
$attachmentName = $uploadedFile->getClientOriginalName();
$attachmentPath = $this->putFileInStorage($attachmentName, $uploadedFile);
$attachmentPath = $this->putFileInStorage($uploadedFile);
$attachment->name = $attachmentName;
$attachment->path = $attachmentPath;
@@ -174,19 +174,18 @@ class AttachmentService extends UploadService
/**
* Store a file in storage with the given filename
* @param $attachmentName
* @param UploadedFile $uploadedFile
* @return string
* @throws FileUploadException
*/
protected function putFileInStorage($attachmentName, UploadedFile $uploadedFile)
protected function putFileInStorage(UploadedFile $uploadedFile)
{
$attachmentData = file_get_contents($uploadedFile->getRealPath());
$storage = $this->getStorage();
$basePath = 'uploads/files/' . Date('Y-m-M') . '/';
$uploadFileName = $attachmentName;
$uploadFileName = str_random(16) . '.' . $uploadedFile->getClientOriginalExtension();
while ($storage->exists($basePath . $uploadFileName)) {
$uploadFileName = str_random(3) . $uploadFileName;
}

View File

@@ -30,5 +30,4 @@ class HttpFetcher
return $data;
}
}
}

View File

@@ -1,5 +1,7 @@
<?php
use BookStack\Auth\Permissions\PermissionService;
use BookStack\Entities\Entity;
use BookStack\Ownable;
/**
@@ -50,21 +52,34 @@ function signedInUser()
* Check if the current user has a permission.
* If an ownable element is passed in the jointPermissions are checked against
* that particular item.
* @param $permission
* @param string $permission
* @param Ownable $ownable
* @return mixed
*/
function userCan($permission, Ownable $ownable = null)
function userCan(string $permission, Ownable $ownable = null)
{
if ($ownable === null) {
return user() && user()->can($permission);
}
// Check permission on ownable item
$permissionService = app(\BookStack\Auth\Permissions\PermissionService::class);
$permissionService = app(PermissionService::class);
return $permissionService->checkOwnableUserAccess($ownable, $permission);
}
/**
* Check if the current user has the given permission
* on any item in the system.
* @param string $permission
* @param string|null $entityClass
* @return bool
*/
function userCanOnAny(string $permission, string $entityClass = null)
{
$permissionService = app(PermissionService::class);
return $permissionService->checkUserHasPermissionOnAnything($permission, $entityClass);
}
/**
* Helper to access system settings.
* @param $key

View File

@@ -52,7 +52,7 @@ return [
'locale' => env('APP_LANG', 'en'),
// Locales available
'locales' => ['en', 'ar', 'de', 'de_informal', 'es', 'es_AR', 'fr', 'nl', 'pt_BR', 'sk', 'sv', 'kr', 'ja', 'pl', 'it', 'ru', 'uk', 'zh_CN', 'zh_TW'],
'locales' => ['en', 'ar', 'de', 'de_informal', 'es', 'es_AR', 'fr', 'nl', 'pt_BR', 'sk', 'cs', 'sv', 'kr', 'ja', 'pl', 'it', 'ru', 'uk', 'zh_CN', 'zh_TW'],
// Application Fallback Locale
'fallback_locale' => 'en',

View File

@@ -8,23 +8,39 @@
* Do not edit this file unless you're happy to maintain any changes yourself.
*/
// REDIS - Split out configuration into an array
// REDIS
// Split out configuration into an array
if (env('REDIS_SERVERS', false)) {
$redisServerKeys = ['host', 'port', 'database'];
$redisDefaults = ['host' => '127.0.0.1', 'port' => '6379', 'database' => '0', 'password' => null];
$redisServers = explode(',', trim(env('REDIS_SERVERS', '127.0.0.1:6379:0'), ','));
$redisConfig = [
'cluster' => env('REDIS_CLUSTER', false)
];
$redisConfig = [];
$cluster = count($redisServers) > 1;
if ($cluster) {
$redisConfig['clusters'] = ['default' => []];
}
foreach ($redisServers as $index => $redisServer) {
$redisServerName = ($index === 0) ? 'default' : 'redis-server-' . $index;
$redisServerDetails = explode(':', $redisServer);
if (count($redisServerDetails) < 2) $redisServerDetails[] = '6379';
if (count($redisServerDetails) < 3) $redisServerDetails[] = '0';
$redisConfig[$redisServerName] = array_combine($redisServerKeys, $redisServerDetails);
$serverConfig = [];
$configIndex = 0;
foreach ($redisDefaults as $configKey => $configDefault) {
$serverConfig[$configKey] = ($redisServerDetails[$configIndex] ?? $configDefault);
$configIndex++;
}
if ($cluster) {
$redisConfig['clusters']['default'][] = $serverConfig;
} else {
$redisConfig['default'] = $serverConfig;
}
}
}
// MYSQL - Split out port from host if set
// 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);

View File

@@ -141,6 +141,7 @@ return [
'user_filter' => env('LDAP_USER_FILTER', '(&(uid=${user}))'),
'version' => env('LDAP_VERSION', false),
'email_attribute' => env('LDAP_EMAIL_ATTRIBUTE', 'mail'),
'display_name_attribute' => env('LDAP_DISPLAY_NAME_ATTRIBUTE', 'cn'),
'follow_referrals' => env('LDAP_FOLLOW_REFERRALS', false),
'user_to_groups' => env('LDAP_USER_TO_GROUPS',false),
'group_attribute' => env('LDAP_GROUP_ATTRIBUTE', 'memberOf'),

12
public/dist/app.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -2245,12 +2245,17 @@ ul.pagination {
float: left !important;
margin: 6px 12px 6px 0; }
.page-content .align-right {
float: right !important; }
text-align: right !important; }
.page-content img.align-right, .page-content table.align-right {
text-align: right;
float: right !important;
margin: 6px 0 6px 12px; }
.page-content .align-center {
text-align: center; }
.page-content img.align-center {
display: block; }
.page-content img.align-center, .page-content table.align-center {
margin-left: auto;
margin-right: auto; }
.page-content img {
max-width: 100%;
height: auto; }

View File

@@ -2876,7 +2876,6 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
.image-manager-sidebar {
width: 300px;
margin-left: 1px;
overflow-y: auto;
overflow-x: hidden;
border-left: 1px solid #DDD; }
@@ -3178,8 +3177,8 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
font-size: 12px;
line-height: 1.2;
top: 88px;
left: -26px;
width: 148px;
left: -12px;
width: 160px;
background: #E84F4F;
padding: 6px;
color: white; }
@@ -3188,7 +3187,7 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
content: '';
position: absolute;
top: -6px;
left: 64px;
left: 44px;
width: 0;
height: 0;
border-left: 6px solid transparent;
@@ -3841,12 +3840,17 @@ ul.pagination {
float: left !important;
margin: 6px 12px 6px 0; }
.page-content .align-right {
float: right !important; }
text-align: right !important; }
.page-content img.align-right, .page-content table.align-right {
text-align: right;
float: right !important;
margin: 6px 0 6px 12px; }
.page-content .align-center {
text-align: center; }
.page-content img.align-center {
display: block; }
.page-content img.align-center, .page-content table.align-center {
margin-left: auto;
margin-right: auto; }
.page-content img {
max-width: 100%;
height: auto; }

View File

@@ -9,8 +9,7 @@ A platform for storing and organising information and documentation. General inf
* [Installation Instructions](https://www.bookstackapp.com/docs/admin/installation)
* [Documentation](https://www.bookstackapp.com/docs)
* [Demo Instance](https://demo.bookstackapp.com)
* *Username: `admin@example.com`*
* *Password: `password`*
* [Admin Login](https://demo.bookstackapp.com/login?email=admin@example.com&password=password)
* [BookStack Blog](https://www.bookstackapp.com/blog)
## Project Definition
@@ -19,7 +18,30 @@ BookStack is an opinionated wiki system that provides a pleasant and simple out
BookStack is not designed as an extensible platform to be used for purposes that differ to the statement above.
In regards to development philosophy, BookStack has a relaxed, open & positive approach. Put simply, At the end of the day this is free software developed and maintained by people donating their own free time.
In regards to development philosophy, BookStack has a relaxed, open & positive approach. At the end of the day this is free software developed and maintained by people donating their own free time.
## Road Map
Below is a high-level road map view for BookStack to provide a sense of direction of where the project is going. This can change at any point and does not reflect many features and improvements that will also be included as part of the journey along this road map. For more granular detail of what will be included in upcoming releases you can review the project milestones as defined in the "Release Process" section below.
- **Design Revamp** *[(In Progress)](https://github.com/BookStackApp/BookStack/pull/1153)*
- *A more organised modern design to clean things up, make BookStack more efficient to use and increase mobile usability.*
- **Platform REST API**
- *A REST API covering, at minimum, control of core content models (Books, Chapters, Pages) for automation and platform extension.*
- **Editor Alignment & Review**
- *Review the page editors with goal of achieving increased interoperability & feature parity while also considering collaborative editing potential.*
- **Permission System Review**
- *Improvement in how permissions are applied and a review of the efficiency of the permission & roles system.*
- **Installation & Deployment Process Revamp**
- *Creation of a streamlined & secure process for users to deploy & update BookStack with reduced development requirements (No git or composer requirement).*
## Release Versioning & Process
BookStack releases are each assigned a version number, such as "v0.25.2", in the format `v<phase>.<feature>.<patch>`. A change only in the `patch` number indicates a fairly minor release that mainly contains fixes and therefore is very unlikely to cause breakages upon update. A change in the `feature` number indicates a release which will generally bring new features in addition to fixes and enhancements. These releases have a small chance of introducing breaking changes upon update so it's worth checking for any notes in the [update guide](https://www.bookstackapp.com/docs/admin/updates/). A change in the `phase` indicates a much large change in BookStack that will likely incur breakages requiring manual intervention.
Each BookStack release will have a [milestone](https://github.com/BookStackApp/BookStack/milestones) created with issues & pull requests assigned to it to define what will be in that release. Milestones are built up then worked through until complete at which point, after some testing and documentation updates, the release will be deployed.
For feature releases, and some patch releases, the release will be accompanied by a post on the [BookStack blog](https://www.bookstackapp.com/blog/) which will provide additional detail on features, changes & updates otherwise the [GitHub release page](https://github.com/BookStackApp/BookStack/releases) will show a list of changes. You can sign up to be alerted to new BookStack blogs posts (once per week maximum) [at this link](http://eepurl.com/cmmq5j).
## Development & Testing
@@ -85,15 +107,15 @@ PHP code within BookStack is generally to [PSR-2](http://www.php-fig.org/psr/psr
### Pull Requests
Pull requests are very welcome. If the scope of your pull request is large it may be best to open the pull request early or create an issue for it to discuss how it will fit in to the project and plan out the merge.
Pull requests are welcome. Unless a small tweak or language update, It may be best to open the pull request early or create an issue for your intended change to discuss how it will fit in to the project and plan out the merge.
Pull requests should be created from the `master` branch and should be merged back into `master` once done. Please do not build from or request a merge into the `release` branch as this is only for publishing releases.
Pull requests should be created from the `master` branch since they will be merged back into `master` once done. Please do not build from or request a merge into the `release` branch as this is only for publishing releases.
If you are looking to alter CSS or JavaScript content please edit the source files found in `resources/assets`. Any CSS or JS files within `public` are built from these source files and therefore should not be edited directly.
## Website, Docs & Blog
The website project docs & Blog can be found in the [BookStackApp/website](https://github.com/BookStackApp/website) repo.
The website which contains the project docs & Blog can be found in the [BookStackApp/website](https://github.com/BookStackApp/website) repo.
## License
@@ -117,7 +139,6 @@ These are the great open-source projects used to help build BookStack:
* [clipboard.js](https://clipboardjs.com/)
* [TinyColorPicker](http://www.dematte.at/tinyColorPicker/index.html)
* [markdown-it](https://github.com/markdown-it/markdown-it) and [markdown-it-task-lists](https://github.com/revin/markdown-it-task-lists)
* [Moment.js](http://momentjs.com/)
* [BarryVD](https://github.com/barryvdh)
* [Debugbar](https://github.com/barryvdh/laravel-debugbar)
* [Dompdf](https://github.com/barryvdh/laravel-dompdf)

View File

@@ -8,7 +8,11 @@ class MarkdownEditor {
constructor(elem) {
this.elem = elem;
this.textDirection = document.getElementById('page-editor').getAttribute('text-direction');
const pageEditor = document.getElementById('page-editor');
this.pageId = pageEditor.getAttribute('page-id');
this.textDirection = pageEditor.getAttribute('text-direction');
this.markdown = new MarkdownIt({html: true});
this.markdown.use(mdTasksLists, {label: true});
@@ -98,7 +102,9 @@ class MarkdownEditor {
}
codeMirrorSetup() {
let cm = this.cm;
const cm = this.cm;
const context = this;
// Text direction
// cm.setOption('direction', this.textDirection);
cm.setOption('direction', 'ltr'); // Will force to remain as ltr for now due to issues when HTML is in editor.
@@ -266,17 +272,18 @@ class MarkdownEditor {
}
// Insert image into markdown
let id = "image-" + Math.random().toString(16).slice(2);
let placeholderImage = window.baseUrl(`/loading.gif#upload${id}`);
let selectedText = cm.getSelection();
let placeHolderText = `![${selectedText}](${placeholderImage})`;
let cursor = cm.getCursor();
const id = "image-" + Math.random().toString(16).slice(2);
const placeholderImage = window.baseUrl(`/loading.gif#upload${id}`);
const selectedText = cm.getSelection();
const placeHolderText = `![${selectedText}](${placeholderImage})`;
const cursor = cm.getCursor();
cm.replaceSelection(placeHolderText);
cm.setCursor({line: cursor.line, ch: cursor.ch + selectedText.length + 3});
let remoteFilename = "image-" + Date.now() + "." + ext;
let formData = new FormData();
const remoteFilename = "image-" + Date.now() + "." + ext;
const formData = new FormData();
formData.append('file', file, remoteFilename);
formData.append('uploaded_to', context.pageId);
window.$http.post('/images/gallery/upload', formData).then(resp => {
const newContent = `[![${selectedText}](${resp.data.thumbs.display})](${resp.data.url})`;
@@ -302,7 +309,7 @@ class MarkdownEditor {
}
actionInsertImage() {
let cursorPos = this.cm.getCursor('from');
const cursorPos = this.cm.getCursor('from');
window.ImageManager.show(image => {
let selectedText = this.cm.getSelection();
let newText = "[![" + (selectedText || image.name) + "](" + image.thumbs.display + ")](" + image.url + ")";
@@ -313,7 +320,7 @@ class MarkdownEditor {
}
actionShowImageManager() {
let cursorPos = this.cm.getCursor('from');
const cursorPos = this.cm.getCursor('from');
window.ImageManager.show(image => {
this.insertDrawing(image, cursorPos);
}, 'drawio');
@@ -321,7 +328,7 @@ class MarkdownEditor {
// Show the popup link selector and insert a link when finished
actionShowLinkSelector() {
let cursorPos = this.cm.getCursor('from');
const cursorPos = this.cm.getCursor('from');
window.EntitySelectorPopup.show(entity => {
let selectedText = this.cm.getSelection() || entity.name;
let newText = `[${selectedText}](${entity.link})`;
@@ -357,7 +364,7 @@ class MarkdownEditor {
}
insertDrawing(image, originalCursor) {
let newText = `<div drawio-diagram="${image.id}"><img src="${image.url}"></div>`;
const newText = `<div drawio-diagram="${image.id}"><img src="${image.url}"></div>`;
this.cm.focus();
this.cm.replaceSelection(newText);
this.cm.setCursor(originalCursor.line, originalCursor.ch + newText.length);
@@ -365,9 +372,13 @@ class MarkdownEditor {
// Show draw.io if enabled and handle save.
actionEditDrawing(imgContainer) {
if (document.querySelector('[drawio-enabled]').getAttribute('drawio-enabled') !== 'true') return;
let cursorPos = this.cm.getCursor('from');
let drawingId = imgContainer.getAttribute('drawio-diagram');
const drawingDisabled = document.querySelector('[drawio-enabled]').getAttribute('drawio-enabled') !== 'true';
if (drawingDisabled) {
return;
}
const cursorPos = this.cm.getCursor('from');
const drawingId = imgContainer.getAttribute('drawio-diagram');
DrawIO.show(() => {
return window.$http.get(window.baseUrl(`/images/base64/${drawingId}`)).then(resp => {

View File

@@ -4,22 +4,24 @@ import DrawIO from "../services/drawio";
/**
* Handle pasting images from clipboard.
* @param {ClipboardEvent} event
* @param {WysiwygEditor} wysiwygComponent
* @param editor
*/
function editorPaste(event, editor) {
function editorPaste(event, editor, wysiwygComponent) {
if (!event.clipboardData || !event.clipboardData.items) return;
let items = event.clipboardData.items;
for (let i = 0; i < items.length; i++) {
if (items[i].type.indexOf("image") === -1) continue;
for (let clipboardItem of event.clipboardData.items) {
if (clipboardItem.type.indexOf("image") === -1) continue;
event.preventDefault();
let id = "image-" + Math.random().toString(16).slice(2);
let loadingImage = window.baseUrl('/loading.gif');
let file = items[i].getAsFile();
const id = "image-" + Math.random().toString(16).slice(2);
const loadingImage = window.baseUrl('/loading.gif');
const file = clipboardItem.getAsFile();
setTimeout(() => {
editor.insertContent(`<p><img src="${loadingImage}" id="${id}"></p>`);
uploadImageFile(file).then(resp => {
uploadImageFile(file, wysiwygComponent).then(resp => {
editor.dom.setAttrib(id, 'src', resp.thumbs.display);
}).catch(err => {
editor.dom.remove(id);
@@ -33,9 +35,12 @@ function editorPaste(event, editor) {
/**
* Upload an image file to the server
* @param {File} file
* @param {WysiwygEditor} wysiwygComponent
*/
function uploadImageFile(file) {
if (file === null || file.type.indexOf('image') !== 0) return Promise.reject(`Not an image file`);
async function uploadImageFile(file, wysiwygComponent) {
if (file === null || file.type.indexOf('image') !== 0) {
throw new Error(`Not an image file`);
}
let ext = 'png';
if (file.name) {
@@ -43,11 +48,13 @@ function uploadImageFile(file) {
if (fileNameMatches.length > 1) ext = fileNameMatches[1];
}
let remoteFilename = "image-" + Date.now() + "." + ext;
let formData = new FormData();
const remoteFilename = "image-" + Date.now() + "." + ext;
const formData = new FormData();
formData.append('file', file, remoteFilename);
formData.append('uploaded_to', wysiwygComponent.pageId);
return window.$http.post(window.baseUrl('/images/gallery/upload'), formData).then(resp => (resp.data));
const resp = await window.$http.post(window.baseUrl('/images/gallery/upload'), formData);
return resp.data;
}
function registerEditorShortcuts(editor) {
@@ -370,7 +377,10 @@ class WysiwygEditor {
constructor(elem) {
this.elem = elem;
this.textDirection = document.getElementById('page-editor').getAttribute('text-direction');
const pageEditor = document.getElementById('page-editor');
this.pageId = pageEditor.getAttribute('page-id');
this.textDirection = pageEditor.getAttribute('text-direction');
this.plugins = "image table textcolor paste link autolink fullscreen imagetools code customhr autosave lists codeeditor media";
this.loadPlugins();
@@ -397,6 +407,9 @@ class WysiwygEditor {
}
getTinyMceConfig() {
const context = this;
return {
selector: '#html-editor',
content_css: [
@@ -586,7 +599,7 @@ class WysiwygEditor {
});
// Paste image-uploads
editor.on('paste', event => editorPaste(event, editor));
editor.on('paste', event => editorPaste(event, editor, context));
}
};
}

View File

@@ -8,6 +8,7 @@ import 'codemirror/mode/diff/diff';
import 'codemirror/mode/go/go';
import 'codemirror/mode/htmlmixed/htmlmixed';
import 'codemirror/mode/javascript/javascript';
import 'codemirror/mode/lua/lua';
import 'codemirror/mode/markdown/markdown';
import 'codemirror/mode/nginx/nginx';
import 'codemirror/mode/php/php';
@@ -38,12 +39,13 @@ const modeMap = {
javascript: 'javascript',
json: {name: 'javascript', json: true},
js: 'javascript',
php: 'php',
lua: 'lua',
md: 'markdown',
mdown: 'markdown',
markdown: 'markdown',
nginx: 'nginx',
powershell: 'powershell',
php: 'php',
py: 'python',
python: 'python',
ruby: 'ruby',

View File

@@ -16,6 +16,7 @@ function mounted() {
addRemoveLinks: true,
dictRemoveFile: trans('components.image_upload_remove'),
timeout: Number(window.uploadTimeout) || 60000,
maxFilesize: Number(window.uploadLimit) || 256,
url: function() {
return _this.uploadUrl;
},

View File

@@ -210,7 +210,6 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
.image-manager-sidebar {
width: 300px;
margin-left: 1px;
overflow-y: auto;
overflow-x: hidden;
border-left: 1px solid #DDD;
@@ -524,8 +523,8 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
font-size: 12px;
line-height: 1.2;
top: 88px;
left: -26px;
width: 148px;
left: -12px;
width: 160px;
background: $negative;
padding: $-xs;
color: white;
@@ -535,7 +534,7 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
content: '';
position: absolute;
top: -6px;
left: 64px;
left: 44px;
width: 0;
height: 0;
border-left: 6px solid transparent;

View File

@@ -51,15 +51,22 @@
margin: $-xs $-s $-xs 0;
}
.align-right {
float: right !important;
text-align: right !important;
}
img.align-right, table.align-right {
text-align: right;
float: right !important;
margin: $-xs 0 $-xs $-s;
}
.align-center {
text-align: center;
}
img.align-center {
display: block;
}
img.align-center, table.align-center {
margin-left: auto;
margin-right: auto;
}
img {
max-width: 100%;
height:auto;

View File

@@ -0,0 +1,48 @@
<?php
/**
* Activity text strings.
* Is used for all the text within activity logs & notifications.
*/
return [
// Pages
'page_create' => 'vytvořená stránka',
'page_create_notification' => 'Stránka byla úspěšně vytvořena',
'page_update' => 'aktualizovaná stránka',
'page_update_notification' => 'Stránka byla úspěšně aktualizována',
'page_delete' => 'smazaná stránka',
'page_delete_notification' => 'Stránka byla úspěšně smazána',
'page_restore' => 'renovovaná stránka',
'page_restore_notification' => 'Stránka byla úspěšně renovována',
'page_move' => 'přesunutá stránka',
// Chapters
'chapter_create' => 'vytvořená kapitola',
'chapter_create_notification' => 'Kapitola byla úspěšně vytvořena',
'chapter_update' => 'aktualizovaná kapitola',
'chapter_update_notification' => 'Kapitola byla úspěšně aktualizována',
'chapter_delete' => 'smazaná kapitola',
'chapter_delete_notification' => 'Kapitola byla úspěšně smazána',
'chapter_move' => 'přesunutá kapitola',
// Books
'book_create' => 'vytvořená kniha',
'book_create_notification' => 'Kniha byla úspěšně vytvořena',
'book_update' => 'aktualizovaná kniha',
'book_update_notification' => 'Kniha byla úspěšně aktualizována',
'book_delete' => 'smazaná kniha',
'book_delete_notification' => 'Kniha byla úspěšně smazána',
'book_sort' => 'seřazená kniha',
'book_sort_notification' => 'Kniha byla úspěšně seřazena',
// Bookshelves
'bookshelf_create' => 'vytvořená knihovna',
'bookshelf_create_notification' => 'Knihovna úspěšně vytvořena',
'bookshelf_update' => 'aktualizovaná knihovna',
'bookshelf_update_notification' => 'Knihovna byla úspěšně aktualizována',
'bookshelf_delete' => 'smazaná knihovna',
'bookshelf_delete_notification' => 'Knihovna byla úspěšně smazána',
// Other
'commented_on' => 'okomentováno v',
];

View File

@@ -0,0 +1,65 @@
<?php
/**
* Authentication Language Lines
* The following language lines are used during authentication for various
* messages that we need to display to the user.
*/
return [
'failed' => 'Neplatné přihlašovací údaje.',
'throttle' => 'Příliš pokusů o přihlášení. Zkuste to prosím znovu za :seconds sekund.',
// Login & Register
'sign_up' => 'Registrace',
'log_in' => 'Přihlášení',
'log_in_with' => 'Přihlásit přes :socialDriver',
'sign_up_with' => 'Registrovat se přes :socialDriver',
'logout' => 'Odhlásit',
'name' => 'Jméno',
'username' => 'Jméno účtu',
'email' => 'Email',
'password' => 'Heslo',
'password_confirm' => 'Potvrdit heslo',
'password_hint' => 'Musí mít víc než 5 znaků',
'forgot_password' => 'Zapomněli jste heslo?',
'remember_me' => 'Neodhlašovat',
'ldap_email_hint' => 'Zadejte email, který chcete přiřadit k tomuto účtu.',
'create_account' => 'Vytvořit účet',
'social_login' => 'Přihlášení přes sociální sítě',
'social_registration' => 'Registrace přes sociální sítě',
'social_registration_text' => 'Registrovat a přihlásit se přes jinou službu',
'register_thanks' => 'Díky za registraci!',
'register_confirm' => 'Zkontrolujte prosím váš email a klikněte na potvrzovací tlačítko pro dokončení registrace do :appName.',
'registrations_disabled' => 'Registrace jsou momentálně pozastaveny',
'registration_email_domain_invalid' => 'Registrace z této emailové domény nejsou povoleny.',
'register_success' => 'Díky za registraci! Jste registrovaní a přihlášení.',
// Password Reset
'reset_password' => 'Resetovat heslo',
'reset_password_send_instructions' => 'Zadejte vaší emailovou adresu a bude vám zaslán odkaz na resetování hesla.',
'reset_password_send_button' => 'Poslat odkaz pro reset hesla',
'reset_password_sent_success' => 'Odkaz na resetování hesla vám byl zaslán na :email.',
'reset_password_success' => 'Vaše heslo bylo úspěšně resetováno.',
'email_reset_subject' => 'Reset hesla do :appName',
'email_reset_text' => 'Tento email jste obdrželi, protože jsme dostali žádost o resetování vašeho hesla k účtu v :appName.',
'email_reset_not_requested' => 'Pokud jste o reset vašeho hesla nežádali, prostě tento dopis smažte a je to.',
// Email Confirmation
'email_confirm_subject' => 'Potvrďte vaši emailovou adresu pro :appName',
'email_confirm_greeting' => 'Díky že jste se přidali do :appName!',
'email_confirm_text' => 'Prosíme potvrďte funkčnost vaší emailové adresy kliknutím na tlačítko níže:',
'email_confirm_action' => 'Potvrdit emailovou adresu',
'email_confirm_send_error' => 'Potvrzení emailové adresy je vyžadováno, ale systém vám nedokázal odeslat email. Kontaktujte správce aby to dal do kupy a potvrzovací email vám dorazil.',
'email_confirm_success' => 'Vaše emailová adresa byla potvrzena!',
'email_confirm_resent' => 'Email s žádostí o potvrzení vaší emailové adresy byl odeslán. Podívejte se do příchozí pošty.',
'email_not_confirmed' => 'Emailová adresa nebyla potvrzena',
'email_not_confirmed_text' => 'Vaše emailová adresa nebyla dosud potvrzena.',
'email_not_confirmed_click_link' => 'Klikněte na odkaz v emailu který jsme vám zaslali ihned po registraci.',
'email_not_confirmed_resend' => 'Pokud nemůžete nalézt email v příchozí poště, můžete si jej nechat poslat znovu pomocí formuláře níže.',
'email_not_confirmed_resend_button' => 'Znovu poslat email pro potvrzení emailové adresy',
];

View File

@@ -0,0 +1,59 @@
<?php
/**
* Common elements found throughout many areas of BookStack.
*/
return [
// Buttons
'cancel' => 'Storno',
'confirm' => 'Potvrdit',
'back' => 'Zpět',
'save' => 'Uložit',
'continue' => 'Pokračovat',
'select' => 'Zvolit',
'more' => 'Více',
// Form Labels
'name' => 'Jméno',
'description' => 'Popis',
'role' => 'Role',
'cover_image' => 'Obrázek na přebal',
'cover_image_description' => 'Obrázek by měl být asi 440 × 250px.',
// Actions
'actions' => 'Akce',
'view' => 'Pohled',
'create' => 'Vytvořit',
'update' => 'Aktualizovat',
'edit' => 'Upravit',
'sort' => 'Řadit',
'move' => 'Přesunout',
'copy' => 'Kopírovat',
'reply' => 'Odpovědět',
'delete' => 'Smazat',
'search' => 'Hledat',
'search_clear' => 'Vyčistit hledání',
'reset' => 'Reset',
'remove' => 'Odstranit',
'add' => 'Přidat',
// Misc
'deleted_user' => 'Smazaný uživatel',
'no_activity' => 'Žádná aktivita k zobrazení',
'no_items' => 'Žádné položky nejsou k mání',
'back_to_top' => 'Zpět na začátek',
'toggle_details' => 'Ukázat detaily',
'toggle_thumbnails' => 'Ukázat náhledy',
'details' => 'Detaily',
'grid_view' => 'Zobrazit dlaždice',
'list_view' => 'Zobrazit seznam',
'default' => 'Výchozí',
// Header
'view_profile' => 'Ukázat profil',
'edit_profile' => 'Upravit profil',
// Email Content
'email_action_help' => 'Pokud se vám nedaří kliknout na tlačítko ":actionText", zkopírujte odkaz níže přímo do webového prohlížeče:',
'email_rights' => 'Všechna práva vyhrazena',
];

View File

@@ -0,0 +1,33 @@
<?php
/**
* Text used in custom JavaScript driven components.
*/
return [
// Image Manager
'image_select' => 'Volba obrázku',
'image_all' => 'Vše',
'image_all_title' => 'Zobrazit všechny obrázky',
'image_book_title' => 'Zobrazit obrázky nahrané k této knize',
'image_page_title' => 'Zobrazit obrázky nahrané k této stránce',
'image_search_hint' => 'Hledat podle názvu obrázku',
'image_uploaded' => 'Nahráno :uploadedDate',
'image_load_more' => 'Načíst další',
'image_image_name' => 'Název obrázku',
'image_delete_used' => 'Tento obrázek je použit v následujících stránkách.',
'image_delete_confirm' => 'Stisknětě smazat ještě jednou pro potvrzení smazání tohoto obrázku.',
'image_select_image' => 'Zvolte obrázek',
'image_dropzone' => 'Přetáhněte sem obrázky myší nebo sem klikněte pro vybrání souboru.',
'images_deleted' => 'Obrázky smazány',
'image_preview' => 'Náhled obrázku',
'image_upload_success' => 'Obrázek byl úspěšně nahrán',
'image_update_success' => 'Podrobnosti o obrázku byly úspěšně aktualizovány',
'image_delete_success' => 'Obrázek byl úspěšně smazán',
'image_upload_remove' => 'Odstranit',
// Code Editor
'code_editor' => 'Upravit kód',
'code_language' => 'Jazyk kódu',
'code_content' => 'Obsah kódu',
'code_save' => 'Uložit kód',
];

View File

@@ -0,0 +1,294 @@
<?php
/**
* Text used for 'Entities' (Document Structure Elements) such as
* Books, Shelves, Chapters & Pages
*/
return [
// Shared
'recently_created' => 'Nedávno vytvořené',
'recently_created_pages' => 'Nedávno vytvořené stránky',
'recently_updated_pages' => 'Nedávno aktualizované stránky',
'recently_created_chapters' => 'Nedávno vytvořené kapitoly',
'recently_created_books' => 'Nedávno vytvořené knihy',
'recently_update' => 'Nedávno aktualizované',
'recently_viewed' => 'Nedávno prohlížené',
'recent_activity' => 'Nedávné činnosti',
'create_now' => 'Vytvořte jí',
'revisions' => 'Revize',
'meta_revision' => 'Revize #:revisionCount',
'meta_created' => 'Vytvořeno :timeLength',
'meta_created_name' => 'Vytvořeno :timeLength uživatelem :user',
'meta_updated' => 'Aktualizováno :timeLength',
'meta_updated_name' => 'Aktualizováno :timeLength uživatelem :user',
'entity_select' => 'Volba prvku',
'images' => 'Obrázky',
'my_recent_drafts' => 'Mé nedávné koncepty',
'my_recently_viewed' => 'Naposledy navštívené',
'no_pages_viewed' => 'Zatím jste nic neshlédli',
'no_pages_recently_created' => 'Zatím nebyly vytvořeny žádné stránky',
'no_pages_recently_updated' => 'Zatím nebyly aktualizovány žádné stránky',
'export' => 'Export',
'export_html' => 'Všeobjímající HTML',
'export_pdf' => 'PDF dokument',
'export_text' => 'Čistý text (txt)',
// Permissions and restrictions
'permissions' => 'Práva',
'permissions_intro' => 'Zaškrtnutím překryjete práva v uživatelských rolích nastavením níže.',
'permissions_enable' => 'Zapnout vlastní práva',
'permissions_save' => 'Uložit práva',
// Search
'search_results' => 'Výsledky hledání',
'search_total_results_found' => 'Nalezen :count výsledek|Nalezeny :count výsledky|Nalezeny :count výsledky|Nalezeny :count výsledky|Nalezeno :count výsledků',
'search_clear' => 'Vyčistit hledání',
'search_no_pages' => 'Žádná stránka neodpovídá hledanému výrazu',
'search_for_term' => 'Hledat :term',
'search_more' => 'Další výsledky',
'search_filters' => 'Filtry hledání',
'search_content_type' => 'Typ obsahu',
'search_exact_matches' => 'Musí obsahovat',
'search_tags' => 'Hledat štítky (tagy)',
'search_options' => 'Volby',
'search_viewed_by_me' => 'Shlédnuto mnou',
'search_not_viewed_by_me' => 'Neshlédnuto mnou',
'search_permissions_set' => 'Sada práv',
'search_created_by_me' => 'Vytvořeno mnou',
'search_updated_by_me' => 'Aktualizováno',
'search_date_options' => 'Volby datumu',
'search_updated_before' => 'Aktualizováno před',
'search_updated_after' => 'Aktualizováno po',
'search_created_before' => 'Vytvořeno před',
'search_created_after' => 'Vytvořeno po',
'search_set_date' => 'Datum',
'search_update' => 'Hledat znovu',
// Shelves
'shelf' => 'Knihovna',
'shelves' => 'Knihovny',
'shelves_long' => 'Knihovny',
'shelves_empty' => 'Žádné knihovny nebyly vytvořeny',
'shelves_create' => 'Vytvořit novou knihovnu',
'shelves_popular' => 'Populární knihovny',
'shelves_new' => 'Nové knihovny',
'shelves_popular_empty' => 'Nejpopulárnější knihovny se objeví zde.',
'shelves_new_empty' => 'Nejnovější knihovny se objeví zde.',
'shelves_save' => 'Uložit knihovnu',
'shelves_books' => 'Knihy v této knihovně',
'shelves_add_books' => 'Přidat knihy do knihovny',
'shelves_drag_books' => 'Knihu přidáte jejím přetažením sem.',
'shelves_empty_contents' => 'Tato knihovna neobsahuje žádné knihy',
'shelves_edit_and_assign' => 'Pro přidáni knih do knihovny stiskněte úprvy.',
'shelves_edit_named' => 'Upravit knihovnu :name',
'shelves_edit' => 'Upravit knihovnu',
'shelves_delete' => 'Smazat knihovnu',
'shelves_delete_named' => 'Smazat knihovnu :name',
'shelves_delete_explain' => "Chystáte se smazat knihovnu ':name'. Knihy v ní obsažené zůstanou zachovány.",
'shelves_delete_confirmation' => 'Opravdu chcete smazat tuto knihovnu?',
'shelves_permissions' => 'Práva knihovny',
'shelves_permissions_updated' => 'Práva knihovny byla aktualizována',
'shelves_permissions_active' => 'Účinná práva knihovny',
'shelves_copy_permissions_to_books' => 'Přenést práva na knihy',
'shelves_copy_permissions' => 'Zkopírovat práva',
'shelves_copy_permissions_explain' => 'Práva knihovny budou aplikována na všechny knihy v ní obsažené. Před použitím se ujistěte, že jste uložili změny práv knihovny.',
'shelves_copy_permission_success' => 'Práva knihovny přenesena na knihy (celkem :count)',
// Books
'book' => 'Kniha',
'books' => 'Knihy',
'x_books' => ':count Kniha|:count Knihy|:count Knihy|:count Knihy|:count Knih',
'books_empty' => 'Žádné knihy nebyly vytvořeny',
'books_popular' => 'Populární knihy',
'books_recent' => 'Nedávné knihy',
'books_new' => 'Nové knihy',
'books_popular_empty' => 'Zde budou zobrazeny nejpopulárnější knihy.',
'books_new_empty' => 'Zde budou zobrazeny nově vytvořené knihy.',
'books_create' => 'Vytvořit novou knihu',
'books_delete' => 'Smazat knihu',
'books_delete_named' => 'Smazat knihu :bookName',
'books_delete_explain' => 'Kniha \':bookName\' bude smazána. Všechny její stránky a kapitoly budou taktéž smazány.',
'books_delete_confirmation' => 'Opravdu chcete tuto knihu smazat.',
'books_edit' => 'Upravit knihu',
'books_edit_named' => 'Upravit knihu :bookName',
'books_form_book_name' => 'Název knihy',
'books_save' => 'Uložit knihu',
'books_permissions' => 'Práva knihy',
'books_permissions_updated' => 'Práva knihy upravena',
'books_empty_contents' => 'V této knize nebyly vytvořeny žádné stránky ani kapitoly.',
'books_empty_create_page' => 'Vytvořit novou stránku',
'books_empty_or' => 'nebo',
'books_empty_sort_current_book' => 'Seřadit tuto knihu',
'books_empty_add_chapter' => 'Přidat kapitolu',
'books_permissions_active' => 'Účinná práva knihy',
'books_search_this' => 'Prohledat tuto knihu',
'books_navigation' => 'Obsah knihy',
'books_sort' => 'Seřadit obsah knihy',
'books_sort_named' => 'Seřadit knihu :bookName',
'books_sort_show_other' => 'Ukázat ostatní knihy',
'books_sort_save' => 'Uložit nové pořadí',
// Chapters
'chapter' => 'Kapitola',
'chapters' => 'Kapitoly',
'x_chapters' => ':count kapitola|:count kapitoly|:count kapitoly|:count kapitoly|:count kapitol',
'chapters_popular' => 'Populární kapitoly',
'chapters_new' => 'Nová kapitola',
'chapters_create' => 'Vytvořit novou kapitolu',
'chapters_delete' => 'Smazat kapitolu',
'chapters_delete_named' => 'Smazat kapitolu :chapterName',
'chapters_delete_explain' => 'Kapitola \':chapterName\' bude smazána. Všechny stránky v ní obsažené budou přesunuty přímo pod samotnou knihu.',
'chapters_delete_confirm' => 'Opravdu chcete tuto kapitolu smazat?',
'chapters_edit' => 'Upravit kapitolu',
'chapters_edit_named' => 'Upravit kapitolu :chapterName',
'chapters_save' => 'Uložit kapitolu',
'chapters_move' => 'Přesunout kapitolu',
'chapters_move_named' => 'Přesunout kapitolu :chapterName',
'chapter_move_success' => 'Kapitola přesunuta do knihy :bookName',
'chapters_permissions' => 'Práva kapitoly',
'chapters_empty' => 'Tato kapitola neobsahuje žádné stránky',
'chapters_permissions_active' => 'Účinná práva kapitoly',
'chapters_permissions_success' => 'Práva kapitoly aktualizována',
'chapters_search_this' => 'Prohledat tuto kapitolu',
// Pages
'page' => 'Stránka',
'pages' => 'Stránky',
'x_pages' => ':count strana|:count strany|:count strany|:count strany|:count stran',
'pages_popular' => 'Populární stránky',
'pages_new' => 'Nová stránka',
'pages_attachments' => 'Přílohy',
'pages_navigation' => 'Obsah stránky',
'pages_delete' => 'Smazat stránku',
'pages_delete_named' => 'Smazat stránku :pageName',
'pages_delete_draft_named' => 'Smazat koncept stránky :pageName',
'pages_delete_draft' => 'Smazat koncept stránky',
'pages_delete_success' => 'Stránka smazána',
'pages_delete_draft_success' => 'Koncept stránky smazán',
'pages_delete_confirm' => 'Opravdu chcete tuto stránku smazat?',
'pages_delete_draft_confirm' => 'Opravdu chcete tento koncept stránky smazat?',
'pages_editing_named' => 'Úpravy stránky :pageName',
'pages_edit_toggle_header' => 'Ukázat hlavičku',
'pages_edit_save_draft' => 'Uložit koncept',
'pages_edit_draft' => 'Upravit koncept stránky',
'pages_editing_draft' => 'Úpravy konceptu',
'pages_editing_page' => 'Úpravy stránky',
'pages_edit_draft_save_at' => 'Koncept uložen v ',
'pages_edit_delete_draft' => 'Smazat koncept',
'pages_edit_discard_draft' => 'Zahodit koncept',
'pages_edit_set_changelog' => 'Zadat komentář ke změnám',
'pages_edit_enter_changelog_desc' => 'Zadejte stručný popis změn, které jste provedli.',
'pages_edit_enter_changelog' => 'Vložit komentáře ke změnám',
'pages_save' => 'Uložit stránku',
'pages_title' => 'Nadpis stránky',
'pages_name' => 'Název stránky',
'pages_md_editor' => 'Editor',
'pages_md_preview' => 'Náhled',
'pages_md_insert_image' => 'Vložit obrázek',
'pages_md_insert_link' => 'Vložit odkaz na prvek',
'pages_md_insert_drawing' => 'Vložit kresbu',
'pages_not_in_chapter' => 'Stránka není součástí žádné kapitoly',
'pages_move' => 'Přesunout stránku',
'pages_move_success' => 'Stránka přesunuta do ":parentName"',
'pages_copy' => 'Kopírovat stránku',
'pages_copy_desination' => 'Cíl kopírování',
'pages_copy_success' => 'Stránka byla úspěšně zkopírována',
'pages_permissions' => 'Práva stránky',
'pages_permissions_success' => 'Práva stránky aktualizována',
'pages_revision' => 'Revize',
'pages_revisions' => 'Revize stránky',
'pages_revisions_named' => 'Revize stránky :pageName',
'pages_revision_named' => 'Revize stránky :pageName',
'pages_revisions_created_by' => 'Vytvořeno uživatelem',
'pages_revisions_date' => 'Datum revize',
'pages_revisions_number' => '#',
'pages_revisions_changelog' => 'Komentáře změn',
'pages_revisions_changes' => 'Změny',
'pages_revisions_current' => 'Aktuální verze',
'pages_revisions_preview' => 'Náhled',
'pages_revisions_restore' => 'Renovovat',
'pages_revisions_none' => 'Tato stránka nemá žádné revize',
'pages_copy_link' => 'Zkopírovat odkaz',
'pages_edit_content_link' => 'Upravit obsah',
'pages_permissions_active' => 'Účinná práva stránky',
'pages_initial_revision' => 'První vydání',
'pages_initial_name' => 'Nová stránka',
'pages_editing_draft_notification' => 'Právě upravujete koncept, který byl uložen před :timeDiff.',
'pages_draft_edited_notification' => 'Tato stránka se od té doby změnila. Je doporučeno aktuální koncept zahodit.',
'pages_draft_edit_active' => [
'start_a' => 'Uživatelé začali upravovat tuto stránku (celkem :count)',
'start_b' => 'Uživatel :userName začal upravovat tuto stránku',
'time_a' => 'od doby, kdy byla tato stránky naposledy aktualizována',
'time_b' => 'v posledních minutách (:minCount min.)',
'message' => ':start :time. Dávejte pozor abyste nepřepsali změny ostatním!',
],
'pages_draft_discarded' => 'Koncept zahozen. Editor nyní obsahuje aktuální verzi stránky.',
'pages_specific' => 'Konkrétní stránka',
// Editor Sidebar
'page_tags' => 'Štítky stránky',
'chapter_tags' => 'Štítky kapitoly',
'book_tags' => 'Štítky knihy',
'shelf_tags' => 'Štítky knihovny',
'tag' => 'Štítek',
'tags' => 'Štítky',
'tag_value' => 'Hodnota Štítku (volitelné)',
'tags_explain' => "Přidejte si štítky pro lepší kategorizaci knih. \n Štítky mohou nést i hodnotu pro detailnější klasifikaci.",
'tags_add' => 'Přidat další štítek',
'attachments' => 'Přílohy',
'attachments_explain' => 'Nahrajte soubory nebo připojte odkazy, které se zobrazí na stránce. Budou k nalezení v postranní liště.',
'attachments_explain_instant_save' => 'Změny zde provedené se okamžitě ukládají.',
'attachments_items' => 'Připojené položky',
'attachments_upload' => 'Nahrát soubor',
'attachments_link' => 'Připojit odkaz',
'attachments_set_link' => 'Nastavit odkaz',
'attachments_delete_confirm' => 'Stiskněte smazat znovu pro potvrzení smazání.',
'attachments_dropzone' => 'Přetáhněte sem soubory myší nebo sem kliknětě pro vybrání souboru.',
'attachments_no_files' => 'Žádné soubory nebyli nahrány',
'attachments_explain_link' => 'Můžete pouze připojit odkaz pokud nechcete nahrávat soubor přímo. Může to být odkaz na jinou stránku nebo na soubor v cloudu.',
'attachments_link_name' => 'Název odkazu',
'attachment_link' => 'Odkaz na přílohu',
'attachments_link_url' => 'Odkaz na soubor',
'attachments_link_url_hint' => 'URL stránky nebo souboru',
'attach' => 'Připojit',
'attachments_edit_file' => 'Upravit soubor',
'attachments_edit_file_name' => 'Název souboru',
'attachments_edit_drop_upload' => 'Přetáhněte sem soubor myší nebo klikněte pro nahrání nového a následné přepsání starého.',
'attachments_order_updated' => 'Pořadí příloh aktualizováno',
'attachments_updated_success' => 'Podrobnosti příloh aktualizovány',
'attachments_deleted' => 'Příloha byla smazána',
'attachments_file_uploaded' => 'Soubor byl úspěšně nahrán',
'attachments_file_updated' => 'Soubor byl úspěšně aktualizován',
'attachments_link_attached' => 'Odkaz úspěšně přiložen ke stránce',
// Profile View
'profile_user_for_x' => 'Uživatelem již :time',
'profile_created_content' => 'Vytvořený obsah',
'profile_not_created_pages' => ':userName nevytvořil/a žádný obsah',
'profile_not_created_chapters' => ':userName nevytvořil/a žádné kapitoly',
'profile_not_created_books' => ':userName nevytvořil/a žádné knihy',
// Comments
'comment' => 'Komentář',
'comments' => 'Komentáře',
'comment_add' => 'Přidat komentář',
'comment_placeholder' => 'Zanechat komentář zde',
'comment_count' => '{0} Bez komentářů|{1} 1 komentář|[2,4] :count komentáře|[5,*] :count komentářů',
'comment_save' => 'Uložit komentář',
'comment_saving' => 'Ukládání komentáře...',
'comment_deleting' => 'Mazání komentáře...',
'comment_new' => 'Nový komentář',
'comment_created' => 'komentováno :createDiff',
'comment_updated' => 'Aktualizováno :updateDiff uživatelem :username',
'comment_deleted_success' => 'Komentář smazán',
'comment_created_success' => 'Komentář přidán',
'comment_updated_success' => 'Komentář aktualizován',
'comment_delete_confirm' => 'Opravdu chcete smazat tento komentář?',
'comment_in_reply_to' => 'Odpověď na :commentId',
// Revision
'revision_delete_confirm' => 'Opravdu chcete smazat tuto revizi?',
'revision_delete_success' => 'Revize smazána',
'revision_cannot_delete_latest' => 'Nelze smazat poslední revizi.'
];

View File

@@ -0,0 +1,84 @@
<?php
/**
* Text shown in error messaging.
*/
return [
// Permissions
'permission' => 'Nemáte povolení přistupovat na dotazovanou stránku.',
'permissionJson' => 'Nemáte povolení k provedení požadované akce.',
// Auth
'error_user_exists_different_creds' => 'Uživatel s emailem :email již existuje ale s jinými přihlašovacími údaji.',
'email_already_confirmed' => 'Emailová adresa již byla potvrzena. Zkuste se přihlásit.',
'email_confirmation_invalid' => 'Tento potvrzovací odkaz již neplatí nebo už byl použit. Zkuste prosím registraci znovu.',
'email_confirmation_expired' => 'Potvrzovací odkaz už neplatí, email s novým odkazem už byl poslán.',
'ldap_fail_anonymous' => 'Přístup k adresáři LDAP jako anonymní uživatel (anonymous bind) selhal',
'ldap_fail_authed' => 'Přístup k adresáři LDAP pomocí zadaného jména (dn) a hesla selhal',
'ldap_extension_not_installed' => 'Není nainstalováno rozšíření LDAP pro PHP',
'ldap_cannot_connect' => 'Nelze se připojit k adresáři LDAP. Prvotní připojení selhalo.',
'social_no_action_defined' => 'Nebyla zvolena žádá akce',
'social_login_bad_response' => "Nastala chyba během přihlašování přes :socialAccount \n:error",
'social_account_in_use' => 'Tento účet na :socialAccount se již používá. Pokuste se s ním přihlásit volbou Přihlásit přes :socialAccount.',
'social_account_email_in_use' => 'Emailová adresa :email se již používá. Pokud máte již máte náš účet, můžete si jej propojit se svým účtem na :socialAccount v nastavení vašeho profilu.',
'social_account_existing' => 'Tento účet na :socialAccount je již propojen s vaším profilem zde.',
'social_account_already_used_existing' => 'Tento účet na :socialAccount je již používán jiným uživatelem.',
'social_account_not_used' => 'Tento účet na :socialAccount není spřažen s žádným uživatelem. Prosím přiřaďtě si jej v nastavení svého profilu.',
'social_account_register_instructions' => 'Pokud ještě nemáte náš účet, můžete se zaregistrovat pomocí vašeho účtu na :socialAccount.',
'social_driver_not_found' => 'Doplněk pro tohoto správce identity nebyl nalezen.',
'social_driver_not_configured' => 'Nastavení vašeho účtu na :socialAccount není správné. :socialAccount musí mít vaše svolení pro naší aplikaci vás přihlásit.',
// System
'path_not_writable' => 'Nelze zapisovat na cestu k souboru :filePath. Zajistěte aby se dalo nahrávat na server.',
'cannot_get_image_from_url' => 'Nelze získat obrázek z adresy :url',
'cannot_create_thumbs' => 'Server nedokáže udělat náhledy. Zkontrolujte, že rozšíření GD pro PHP je nainstalováno.',
'server_upload_limit' => 'Server nepovoluje nahrávat tak veliké soubory. Zkuste prosím menší soubor.',
'uploaded' => 'Server nepovoluje nahrávat tak veliké soubory. Zkuste prosím menší soubor.', //TODO to je nějaký podezřelý
'image_upload_error' => 'Nastala chyba během nahrávání souboru',
'image_upload_type_error' => 'Typ nahrávaného obrázku je neplatný.',
'file_upload_timeout' => 'Nahrávání souboru trvalo příliš dlouho a tak bylo ukončeno.',
// Attachments
'attachment_page_mismatch' => 'Došlo ke zmatení stránky během nahrávání přílohy.',
'attachment_not_found' => 'Příloha nenalezena',
// Pages
'page_draft_autosave_fail' => 'Nepovedlo se uložit koncept. Než stránku uložíte, ujistěte se, že jste připojeni k internetu.',
'page_custom_home_deletion' => 'Nelze smazat tuto stránku, protože je nastavena jako uvítací stránka.',
// Entities
'entity_not_found' => 'Prvek nenalezen',
'bookshelf_not_found' => 'Knihovna nenalezena',
'book_not_found' => 'Kniha nenalezena',
'page_not_found' => 'Stránka nenalezena',
'chapter_not_found' => 'Kapitola nenalezena',
'selected_book_not_found' => 'Vybraná kniha nebyla nalezena',
'selected_book_chapter_not_found' => 'Zvolená kniha nebo kapitola nebyla nalezena',
'guests_cannot_save_drafts' => 'Návštěvníci z řad veřejnosti nemohou ukládat koncepty.',
// Users
'users_cannot_delete_only_admin' => 'Nemůžete smazat posledního administrátora',
'users_cannot_delete_guest' => 'Uživatele host není možno smazat',
// Roles
'role_cannot_be_edited' => 'Tuto roli nelze editovat',
'role_system_cannot_be_deleted' => 'Toto je systémová role a nelze jí smazat.',
'role_registration_default_cannot_delete' => 'Tuto roli nelze smazat dokud je nastavená jako výchozí role pro registraci nových uživatelů.',
'role_cannot_remove_only_admin' => 'Tento uživatel má roli administrátora. Přiřaďte roli administrátora někomu jinému než jí odeberete zde.',
// Comments
'comment_list' => 'Při dotahování komentářů nastala chyba.',
'cannot_add_comment_to_draft' => 'Nemůžete přidávat komentáře ke konceptu.',
'comment_add' => 'Při přidávání / aktualizaci komentáře nastala chyba.',
'comment_delete' => 'Při mazání komentáře nastala chyba.',
'empty_comment' => 'Nemůžete přidat prázdný komentář.', //This has a deep thinking value
// Error pages
'404_page_not_found' => 'Stránka nenalezena',
'sorry_page_not_found' => 'Omlouváme se, ale stránka, kterou hledáte nebyla nalezena.',
'return_home' => 'Návrat domů',
'error_occurred' => 'Nastala chyba',
'app_down' => ':appName je momentálně vypnutá',
'back_soon' => 'Brzy naběhne.',
];

View File

@@ -0,0 +1,12 @@
<?php
/**
* Pagination Language Lines
* The following language lines are used by the paginator library to build
* the simple pagination links.
*/
return [
'previous' => '&laquo; Pøedchozí',
'next' => 'Další &raquo;',
];

View File

@@ -0,0 +1,15 @@
<?php
/**
* Password Reminder Language Lines
* The following language lines are the default lines which match reasons
* that are given by the password broker for a password update attempt has failed.
*/
return [
'password' => 'Heslo musí být alespoň 6 znaků dlouhé a shodovat se v obou polích.',
'user' => "Nemůžeme najít uživatele se zadanou emailovou adresou.",
'token' => 'Tento odkaz pro reset hesla je neplatný.',
'sent' => 'Poslali jsme vám odkaz pro reset hesla!',
'reset' => 'Vaše heslo bylo resetováno!',
];

View File

@@ -0,0 +1,117 @@
<?php
/**
* Settings text strings
* Contains all text strings used in the general settings sections of BookStack
* including users and roles.
*/
return [
// Common Messages
'settings' => 'Nastavení',
'settings_save' => 'Uložit nastavení',
'settings_save_success' => 'Nastavení bylo uloženo',
// App Settings
'app_settings' => 'Nastavení aplikace',
'app_name' => 'Název aplikace',
'app_name_desc' => 'Název se bude zobrazovat v záhlaví této aplikace a v odesílaných emailech.',
'app_name_header' => 'Zobrazovát název aplikace v záhlaví?',
'app_public_viewing' => 'Povolit prohlížení veřejností?',
'app_secure_images' => 'Nahrávat obrázky neveřejně a zabezpečeně?',
'app_secure_images_desc' => 'Z výkonnostních důvodů jsou všechny obrázky veřejné. Tato volba přidá do adresy obrázku náhodné číslo, aby nikdo neodhadnul adresu obrázku. Zajistěte ať adresáře nikomu nezobrazují seznam souborů.',
'app_editor' => 'Editor stránek',
'app_editor_desc' => 'Zvolte který editor budou užívat všichni uživatelé k úpravě stránek.',
'app_custom_html' => 'Vlastní HTML kód pro sekci hlavičky (<head>).',
'app_custom_html_desc' => 'Cokoliv sem napíšete bude přidáno na konec sekce <head> v každém místě této aplikace. To se hodí pro přidávání nebo změnu CSS stylů nebo přidání kódu pro analýzu používání (např.: google analytics.).',
'app_logo' => 'Logo aplikace',
'app_logo_desc' => 'Obrázek by měl mít 43 pixelů na výšku. <br>Větší obrázky zmenšíme na tuto velikost.',
'app_primary_color' => 'Hlavní barva aplikace',
'app_primary_color_desc' => 'Zápis by měl být hexa (#aabbcc). <br>Pro základní barvu nechte pole prázdné.',
'app_homepage' => 'Úvodní stránka aplikace',
'app_homepage_desc' => 'Zvolte pohled který se objeví jako úvodní stránka po přihlášení. Pokud zvolíte stránku, její specifická oprávnění budou ignorována (výjimka z výjimky 😜).',
'app_homepage_select' => 'Zvolte stránku',
'app_disable_comments' => 'Zakázání komentářů',
'app_disable_comments_desc' => 'Zakáže komentáře napříč všemi stránkami. Existující komentáře se přestanou zobrazovat.',
// Registration Settings
'reg_settings' => 'Nastavení registrace',
'reg_allow' => 'Povolit registrace?',
'reg_default_role' => 'Role přiřazená po registraci',
'reg_confirm_email' => 'Vyžadovat ověření emailové adresy?',
'reg_confirm_email_desc' => 'Pokud zapnete omezení emailové domény, tak bude ověřování emailové adresy vyžadováno vždy.',
'reg_confirm_restrict_domain' => 'Omezit registraci podle domény',
'reg_confirm_restrict_domain_desc' => 'Zadejte emailové domény, kterým bude povolena registrace uživatelů. Oddělujete čárkou. Uživatelům bude odeslán email s odkazem pro potvrzení vlastnictví emailové adresy. Bez potvrzení nebudou moci aplikaci používat. <br> Pozn.: Uživatelé si mohou emailovou adresu změnit po úspěšné registraci.',
'reg_confirm_restrict_domain_placeholder' => 'Žádná omezení nebyla nastvena',
// Maintenance settings
'maint' => 'Údržba',
'maint_image_cleanup' => 'Pročistění obrázků',
'maint_image_cleanup_desc' => "Prohledá stránky a jejich revize, aby zjistil, které obrázky a kresby jsou momentálně používány a které jsou zbytečné. Zajistěte plnou zálohu databáze a obrázků než se do toho pustíte.",
'maint_image_cleanup_ignore_revisions' => 'Ignorovat obrázky v revizích',
'maint_image_cleanup_run' => 'Spustit pročištění',
'maint_image_cleanup_warning' => 'Nalezeno :count potenciálně nepoužitých obrázků. Jste si jistí, že je chcete smazat?',
'maint_image_cleanup_success' => 'Potenciálně nepoužité obrázky byly smazány. Celkem :count.',
'maint_image_cleanup_nothing_found' => 'Žádné potenciálně nepoužité obrázky nebyly nalezeny. Nic nebylo smazáno.',
// Role Settings
'roles' => 'Role',
'role_user_roles' => 'Uživatelské role',
'role_create' => 'Vytvořit novou roli',
'role_create_success' => 'Role byla úspěšně vytvořena',
'role_delete' => 'Smazat roli',
'role_delete_confirm' => 'Role \':roleName\' bude smazána.',
'role_delete_users_assigned' => 'Role je přiřazena :userCount uživatelům. Pokud jim chcete náhradou přidělit jinou roli, zvolte jednu z následujících.',
'role_delete_no_migration' => "Nepřiřazovat uživatelům náhradní roli",
'role_delete_sure' => 'Opravdu chcete tuto roli smazat?',
'role_delete_success' => 'Role byla úspěšně smazána',
'role_edit' => 'Upravit roli',
'role_details' => 'Detaily role',
'role_name' => 'Název role',
'role_desc' => 'Stručný popis role',
'role_external_auth_id' => 'Přihlašovací identifikátory třetích stran',
'role_system' => 'Systémová oprávnění',
'role_manage_users' => 'Správa úživatelů',
'role_manage_roles' => 'Správa rolí a jejich práv',
'role_manage_entity_permissions' => 'Správa práv všech knih, kapitol a stránek',
'role_manage_own_entity_permissions' => 'Správa práv vlastních knih, kapitol a stránek',
'role_manage_settings' => 'Správa nastavení aplikace',
'role_asset' => 'Práva děl',
'role_asset_desc' => 'Tato práva řídí přístup k dílům v rámci systému. Specifická práva na knihách, kapitolách a stránkách překryjí tato nastavení.',
'role_asset_admins' => 'Administrátoři automaticky dostávají přístup k veškerému obsahu, ale tyto volby mohou ukázat nebo skrýt volby v uživatelském rozhraní.',
'role_all' => 'Vše',
'role_own' => 'Vlastní',
'role_controlled_by_asset' => 'Řídí se dílem do kterého jsou nahrávány',
'role_save' => 'Uloži roli',
'role_update_success' => 'Role úspěšně aktualizována',
'role_users' => 'Uživatelé mající tuto roli',
'role_users_none' => 'Žádný uživatel nemá tuto roli.',
// Users
'users' => 'Uživatelé',
'user_profile' => 'Profil uživatele',
'users_add_new' => 'Přidat nového uživatele',
'users_search' => 'Vyhledávání uživatelů',
'users_role' => 'Uživatelské role',
'users_external_auth_id' => 'Přihlašovací identifikátory třetích stran',
'users_password_warning' => 'Vyplňujte pouze v případě, že chcete heslo změnit:',
'users_system_public' => 'Symbolizuje libovolného veřejného návštěvníka, který navštívil vaší aplikaci. Nelze ho použít k přihlášení ale je přiřazen automaticky veřejnosti.',
'users_delete' => 'Smazat uživatele',
'users_delete_named' => 'Smazat uživatele :userName',
'users_delete_warning' => 'Uživatel \':userName\' bude úplně smazán ze systému.',
'users_delete_confirm' => 'Opravdu chcete tohoto uživatele smazat?',
'users_delete_success' => 'Uživatel byl úspěšně smazán',
'users_edit' => 'Upravit uživatele',
'users_edit_profile' => 'Upravit profil',
'users_edit_success' => 'Uživatel byl úspěšně aktualizován',
'users_avatar' => 'Uživatelský obrázek',
'users_avatar_desc' => 'Obrázek by měl být čtverec 256 pixelů široký. Bude oříznut do kruhu.',
'users_preferred_language' => 'Upřednostňovaný jazyk',
'users_social_accounts' => 'Přidružené účty ze sociálních sítí',
'users_social_accounts_info' => 'Zde můžete přidat vaše účty ze sociálních sítí pro pohodlnější přihlašování. Zrušení přidružení zde neznamená, že tato aplikace pozbude práva číst detaily z vašeho účtu. Zakázat této aplikaci přístup k detailům vašeho účtu musíte přímo ve vašem profilu na dané sociální síti.',
'users_social_connect' => 'Přidružit účet',
'users_social_disconnect' => 'Zrušit přidružení',
'users_social_connected' => 'Účet :socialAccount byl úspěšně přidružen k vašemu profilu.',
'users_social_disconnected' => 'Přidružení účtu :socialAccount k vašemu profilu bylo úspěšně zrušeno.'
];

View File

@@ -0,0 +1,152 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Validation Language Lines
|--------------------------------------------------------------------------
|
| The following language lines contain the default error messages used by
| the validator class. Some of these rules have multiple versions such
| as the size rules. Feel free to tweak each of these messages.
|
*/
'accepted' => ':attribute musí být přijat.',
'active_url' => ':attribute není platnou URL adresou.',
'after' => ':attribute musí být datum po :date.',
'after_or_equal' => ':attribute musí být datum :date nebo pozdější.',
'alpha' => ':attribute může obsahovat pouze písmena.',
'alpha_dash' => ':attribute může obsahovat pouze písmena, číslice, pomlčky a podtržítka. České znaky (á, é, í, ó, ú, ů, ž, š, č, ř, ď, ť, ň) nejsou podporovány.',
'alpha_num' => ':attribute může obsahovat pouze písmena a číslice.',
'array' => ':attribute musí být pole.',
'before' => ':attribute musí být datum před :date.',
'before_or_equal' => 'Datum :attribute musí být před nebo rovno :date.',
'between' => [
'numeric' => ':attribute musí být hodnota mezi :min a :max.',
'file' => ':attribute musí být větší než :min a menší než :max Kilobytů.',
'string' => ':attribute musí být delší než :min a kratší než :max znaků.',
'array' => ':attribute musí obsahovat nejméně :min a nesmí obsahovat více než :max prvků.',
],
'boolean' => ':attribute musí být true nebo false',
'confirmed' => ':attribute nesouhlasí.',
'date' => ':attribute musí být platné datum.',
'date_equals' => 'The :attribute must be a date equal to :date.',
'date_format' => ':attribute není platný formát data podle :format.',
'different' => ':attribute a :other se musí lišit.',
'digits' => ':attribute musí být :digits pozic dlouhé.',
'digits_between' => ':attribute musí být dlouhé nejméně :min a nejvíce :max pozic.',
'dimensions' => ':attribute má neplatné rozměry.',
'distinct' => ':attribute má duplicitní hodnotu.',
'email' => ':attribute není platný formát.',
'exists' => 'Zvolená hodnota pro :attribute není platná.',
'file' => ':attribute musí být soubor.',
'filled' => ':attribute musí být vyplněno.',
'gt' => [
'numeric' => ':attribute musí být větší než :value.',
'file' => 'Velikost souboru :attribute musí být větší než :value kB.',
'string' => 'Počet znaků :attribute musí být větší :value.',
'array' => 'Pole :attribute musí mít více prvků než :value.',
],
'gte' => [
'numeric' => ':attribute musí být větší nebo rovno :value.',
'file' => 'Velikost souboru :attribute musí být větší nebo rovno :value kB.',
'string' => 'Počet znaků :attribute musí být větší nebo rovno :value.',
'array' => 'Pole :attribute musí mít :value prvků nebo více.',
],
'image' => ':attribute musí být obrázek.',
'in' => 'Zvolená hodnota pro :attribute je neplatná.',
'in_array' => ':attribute není obsažen v :other.',
'integer' => ':attribute musí být celé číslo.',
'ip' => ':attribute musí být platnou IP adresou.',
'ipv4' => ':attribute musí být platná IPv4 adresa.',
'ipv6' => ':attribute musí být platná IPv6 adresa.',
'json' => ':attribute musí být platný JSON řetězec.',
'lt' => [
'numeric' => ':attribute musí být menší než :value.',
'file' => 'Velikost souboru :attribute musí být menší než :value kB.',
'string' => ':attribute musí obsahovat méně než :value znaků.',
'array' => ':attribute by měl obsahovat méně než :value položek.',
],
'lte' => [
'numeric' => ':attribute musí být menší nebo rovno než :value.',
'file' => 'Velikost souboru :attribute musí být menší než :value kB.',
'string' => ':attribute nesmí být delší než :value znaků.',
'array' => ':attribute by měl obsahovat maximálně :value položek.',
],
'max' => [
'numeric' => ':attribute nemůže být větší než :max.',
'file' => 'Velikost souboru :attribute musí být menší než :value kB.',
'string' => ':attribute nemůže být delší než :max znaků.',
'array' => ':attribute nemůže obsahovat více než :max prvků.',
],
'mimes' => ':attribute musí být jeden z následujících datových typů :values.',
'mimetypes' => ':attribute musí být jeden z následujících datových typů :values.',
'min' => [
'numeric' => ':attribute musí být větší než :min.',
'file' => ':attribute musí být větší než :min kB.',
'string' => ':attribute musí být delší než :min znaků.',
'array' => ':attribute musí obsahovat více než :min prvků.',
],
'not_in' => 'Zvolená hodnota pro :attribute je neplatná.',
'not_regex' => ':attribute musí být regulární výraz.',
'numeric' => ':attribute musí být číslo.',
'present' => ':attribute musí být vyplněno.',
'regex' => ':attribute nemá správný formát.',
'required' => ':attribute musí být vyplněno.',
'required_if' => ':attribute musí být vyplněno pokud :other je :value.',
'required_unless' => ':attribute musí být vyplněno dokud :other je v :values.',
'required_with' => ':attribute musí být vyplněno pokud :values je vyplněno.',
'required_with_all' => ':attribute musí být vyplněno pokud :values je zvoleno.',
'required_without' => ':attribute musí být vyplněno pokud :values není vyplněno.',
'required_without_all' => ':attribute musí být vyplněno pokud není žádné z :values zvoleno.',
'same' => ':attribute a :other se musí shodovat.',
'size' => [
'numeric' => ':attribute musí být přesně :size.',
'file' => ':attribute musí mít přesně :size Kilobytů.',
'string' => ':attribute musí být přesně :size znaků dlouhý.',
'array' => ':attribute musí obsahovat právě :size prvků.',
],
'starts_with' => 'The :attribute must start with one of the following: :values',
'string' => ':attribute musí být řetězec znaků.',
'timezone' => ':attribute musí být platná časová zóna.',
'unique' => ':attribute musí být unikátní.',
'uploaded' => 'Nahrávání :attribute se nezdařilo.',
'url' => 'Formát :attribute je neplatný.',
'uuid' => ':attribute musí být validní UUID.',
/*
|--------------------------------------------------------------------------
| Custom Validation Language Lines
|--------------------------------------------------------------------------
|
| Here you may specify custom validation messages for attributes using the
| convention "attribute.rule" to name the lines. This makes it quick to
| specify a specific custom language line for a given attribute rule.
|
*/
'custom' => [
'attribute-name' => [
'rule-name' => 'custom-message',
],
'password-confirm' => [
'required_with' => 'Password confirmation required',
],
],
/*
|--------------------------------------------------------------------------
| Custom Validation Attributes
|--------------------------------------------------------------------------
|
| The following language lines are used to swap attribute place-holders
| with something more reader friendly such as E-Mail Address instead
| of "email". This simply helps us make messages a little cleaner.
|
*/
'attributes' => [
'password' => 'heslo',
],
];

View File

@@ -60,6 +60,39 @@ return [
'search_created_after' => 'Erstellt nach',
'search_set_date' => 'Datum auswählen',
'search_update' => 'Suche aktualisieren',
/*
* Shelves
*/
'shelf' => 'Regal',
'shelves' => 'Regale',
'shelves_long' => 'Bücherregal',
'shelves_empty' => 'Es wurden noch keine Regale angelegt',
'shelves_create' => 'Erzeuge ein Regal',
'shelves_popular' => 'Beliebte Regale',
'shelves_new' => 'Kürzlich erstellte Regale',
'shelves_popular_empty' => 'Die beliebtesten Regale werden hier angezeigt.',
'shelves_new_empty' => 'Die neusten Regale werden hier angezeigt.',
'shelves_save' => 'Regal speichern',
'shelves_books' => 'Bücher in diesem Regal',
'shelves_add_books' => 'Buch zu diesem Regal hinzufügen',
'shelves_drag_books' => 'Bücher hier hin ziehen um sie dem Regal hinzuzufügen',
'shelves_empty_contents' => 'Diesem Regal sind keine Bücher zugewiesen',
'shelves_edit_and_assign' => 'Regal bearbeiten um Bücher hinzuzufügen',
'shelves_edit_named' => 'Bücherregal :name bearbeiten',
'shelves_edit' => 'Bücherregal bearbeiten',
'shelves_delete' => 'Bücherregal löschen',
'shelves_delete_named' => 'Bücherregal :name löschen',
'shelves_delete_explain' => "Sie sind im Begriff das Bücherregal mit dem Namen ':name' zu löschen. Enthaltene Bücher werden nicht gelöscht.",
'shelves_delete_confirmation' => 'Sind Sie sicher, dass Sie dieses Bücherregal löschen wollen?',
'shelves_permissions' => 'Regal-Berechtigungen',
'shelves_permissions_updated' => 'Regal-Berechtigungen aktualisiert',
'shelves_permissions_active' => 'Regal-Berechtigungen aktiv',
'shelves_copy_permissions_to_books' => 'Kopiere die Berechtigungen zum Buch',
'shelves_copy_permissions' => 'Berechtigungen kopieren',
'shelves_copy_permissions_explain' => 'Hiermit werden die Berechtigungen des aktuellen Regals auf alle enthaltenen Bücher übertragen. Überprüfen Sie vor der Aktivierung, ob alle Berechtigungsänderungen am aktuellen Regal gespeichert wurden.',
'shelves_copy_permission_success' => 'Regal-Berechtigungen wurden zu :count Büchern kopiert',
/**
* Books
*/

View File

@@ -9,6 +9,13 @@ return [
'no_pages_recently_created' => 'Du hast bisher keine Seiten angelegt.',
'no_pages_recently_updated' => 'Du hast bisher keine Seiten aktualisiert.',
/**
* Shelves
*/
'shelves_delete_explain' => "Du bist im Begriff das Bücherregal mit dem Namen ':name' zu löschen. Enthaltene Bücher werden nicht gelöscht.",
'shelves_delete_confirmation' => 'Bist du sicher, dass du dieses Bücherregal löschen willst?',
'shelves_copy_permissions_explain' => 'Hiermit werden die Berechtigungen des aktuellen Regals auf alle enthaltenen Bücher übertragen. Überprüfe vor der Aktivierung, ob alle Berechtigungsänderungen am aktuellen Regal gespeichert wurden.',
/**
* Books
*/

View File

@@ -128,6 +128,7 @@ return [
'nl' => 'Nederlands',
'pt_BR' => 'Português do Brasil',
'sk' => 'Slovensky',
'cs' => 'Česky',
'sv' => 'Svenska',
'kr' => '한국어',
'ja' => '日本語',

View File

@@ -33,6 +33,7 @@ return [
'filled' => 'The :attribute field is required.',
'exists' => 'The selected :attribute is invalid.',
'image' => 'The :attribute must be an image.',
'image_extension' => 'The :attribute must have a valid & supported image extension.',
'in' => 'The selected :attribute is invalid.',
'integer' => 'The :attribute must be an integer.',
'ip' => 'The :attribute must be a valid IP address.',
@@ -49,6 +50,7 @@ return [
'string' => 'The :attribute must be at least :min characters.',
'array' => 'The :attribute must have at least :min items.',
],
'no_double_extension' => 'The :attribute must only have a single file extension.',
'not_in' => 'The selected :attribute is invalid.',
'numeric' => 'The :attribute must be a number.',
'regex' => 'The :attribute format is invalid.',

View File

@@ -66,6 +66,7 @@ return [
'role_cannot_be_edited' => 'Este rol no puede ser editado',
'role_system_cannot_be_deleted' => 'Este rol es un rol de sistema y no puede ser borrado',
'role_registration_default_cannot_delete' => 'Este rol no puede ser borrado mientras sea el rol por defecto de registro',
'role_cannot_remove_only_admin' => 'Este usuario es el único asignado al rol de administrador. Asigne el rol de administrador a otro usuario antes de intentar eliminarlo.',
// Comments
'comment_list' => 'Se produjo un error al obtener los comentarios.',

View File

@@ -74,6 +74,7 @@ return [
'timezone' => 'El atributo :attribute debe ser una zona válida.',
'unique' => 'El atributo :attribute ya ha sido tomado.',
'url' => 'El atributo :attribute tiene un formato inválid.',
'is_image' => 'El atributo :attribute debe ser una imagen válida.',
/*
|--------------------------------------------------------------------------

View File

@@ -27,7 +27,7 @@ return [
'email' => 'Email',
'password' => 'Wachtwoord',
'password_confirm' => 'Wachtwoord Bevestigen',
'password_hint' => 'Minimaal 5 tekens',
'password_hint' => 'Minimaal 6 tekens',
'forgot_password' => 'Wachtwoord vergeten?',
'remember_me' => 'Mij onthouden',
'ldap_email_hint' => 'Geef een email op waarmee je dit account wilt gebruiken.',
@@ -73,4 +73,4 @@ return [
'email_not_confirmed_click_link' => 'Klik op de link in de e-mail die vlak na je registratie is verstuurd.',
'email_not_confirmed_resend' => 'Als je deze e-mail niet kunt vinden kun je deze met onderstaande formulier opnieuw verzenden.',
'email_not_confirmed_resend_button' => 'Bevestigingsmail Opnieuw Verzenden',
];
];

View File

@@ -100,6 +100,38 @@ return [
'books_sort_show_other' => 'Показать другие книги',
'books_sort_save' => 'Сохранить новый порядок',
/**
* Shelves
*/
'shelf' => 'Полка',
'shelves' => 'Полки',
'shelves_long' => 'Книжные полки',
'shelves_empty' => 'Полки не созданы',
'shelves_create' => 'Создать новую полку',
'shelves_popular' => 'Популярные полки',
'shelves_new' => 'Новые полки',
'shelves_popular_empty' => 'Популярные полки появятся здесь.',
'shelves_new_empty' => 'Последние созданные полки появятся здесь.',
'shelves_save' => 'Сохранить полку',
'shelves_books' => 'Книги из этой полки',
'shelves_add_books' => 'Добавить книгу в эту полку',
'shelves_drag_books' => 'Перетащите книгу сюда, чтобы добавить на эту полку',
'shelves_empty_contents' => 'На этой полке нет книг',
'shelves_edit_and_assign' => 'Изменить полку для привязки книг',
'shelves_edit_named' => 'Редактировать полку :name',
'shelves_edit' => 'Редактировать книжную полку',
'shelves_delete' => 'Удалить книжную полку',
'shelves_delete_named' => 'Удалить книжную полку :name',
'shelves_delete_explain' => "Это приведет к удалению полки с именем ':name'. Привязанные книги удалены не будут.",
'shelves_delete_confirmation' => 'Вы уверены, что хотите удалить эту полку?',
'shelves_permissions' => 'Доступы к книжной полке',
'shelves_permissions_updated' => 'Доступы к книжной полке обновлены',
'shelves_permissions_active' => 'Доступы к книжной полке активны',
'shelves_copy_permissions_to_books' => 'Наследовать доступы книгам',
'shelves_copy_permissions' => 'Копировать доступы',
'shelves_copy_permissions_explain' => 'Это применит текущие настройки доступов этой книжной полки ко всем книгам, содержащимся внутри. Перед активацией убедитесь, что все изменения в доступах этой книжной полки сохранены.',
'shelves_copy_permission_success' => 'Доступы книжной полки скопированы для :count books',
/**
* Chapters
*/
@@ -206,6 +238,7 @@ return [
'page_tags' => 'Теги страницы',
'chapter_tags' => 'Теги главы',
'book_tags' => 'Теги книги',
'shelf_tags' => 'Теги полки',
'tag' => 'Тег',
'tags' => 'Теги',
'tag_value' => 'Значение тега (опционально)',

View File

@@ -48,7 +48,7 @@
</form>
</div>
<div class="links text-center">
@if(userCan('bookshelf-view-all') || userCan('bookshelf-view-own'))
@if(userCanOnAny('view', \BookStack\Entities\Bookshelf::class) || userCan('bookshelf-view-own'))
<a href="{{ baseUrl('/shelves') }}">@icon('bookshelf'){{ trans('entities.shelves') }}</a>
@endif
<a href="{{ baseUrl('/books') }}">@icon('book'){{ trans('entities.books') }}</a>

View File

@@ -21,7 +21,9 @@
<a @click="updateLanguage('Java')">Java</a>
<a @click="updateLanguage('JavaScript')">JavaScript</a>
<a @click="updateLanguage('JSON')">JSON</a>
<a @click="updateLanguage('Lua')">Lua</a>
<a @click="updateLanguage('PHP')">PHP</a>
<a @click="updateLanguage('Powershell')">Powershell</a>
<a @click="updateLanguage('MarkDown')">MarkDown</a>
<a @click="updateLanguage('Nginx')">Nginx</a>
<a @click="updateLanguage('Python')">Python</a>
@@ -48,4 +50,4 @@
</div>
</div>
</div>
</div>

View File

@@ -17,15 +17,17 @@
@if(userCan('page-update', $page))
<a href="{{ $page->getUrl('/edit') }}" class="text-primary text-button" >@icon('edit'){{ trans('common.edit') }}</a>
@endif
@if(userCan('page-update', $page) || userCan('restrictions-manage', $page) || userCan('page-delete', $page))
@if((userCan('page-view', $page) && userCanOnAny('page-create')) || userCan('page-update', $page) || userCan('restrictions-manage', $page) || userCan('page-delete', $page))
<div dropdown class="dropdown-container">
<a dropdown-toggle class="text-primary text-button">@icon('more') {{ trans('common.more') }}</a>
<ul>
@if(userCan('page-update', $page))
@if(userCanOnAny('page-create'))
<li><a href="{{ $page->getUrl('/copy') }}" class="text-primary" >@icon('copy'){{ trans('common.copy') }}</a></li>
@if(userCan('page-delete', $page))
<li><a href="{{ $page->getUrl('/move') }}" class="text-primary" >@icon('folder'){{ trans('common.move') }}</a></li>
@endif
@endif
@if(userCan('page-delete', $page) && userCan('page-update', $page))
<li><a href="{{ $page->getUrl('/move') }}" class="text-primary" >@icon('folder'){{ trans('common.move') }}</a></li>
@endif
@if(userCan('page-update', $page))
<li><a href="{{ $page->getUrl('/revisions') }}" class="text-primary">@icon('history'){{ trans('entities.revisions') }}</a></li>
@endif
@if(userCan('restrictions-manage', $page))

View File

@@ -38,8 +38,9 @@
<div class="form-group">
<label for="user-language">{{ trans('settings.users_preferred_language') }}</label>
<select name="setting[language]" id="user-language">
@foreach(trans('settings.language_select') as $lang => $label)
<option @if(setting()->getUser($user, 'language') === $lang) selected @endif value="{{ $lang }}">{{ $label }}</option>
<option @if(setting()->getUser($user, 'language', config('app.default_locale')) === $lang) selected @endif value="{{ $lang }}">{{ $label }}</option>
@endforeach
</select>
</div>

View File

@@ -23,6 +23,7 @@ class LdapTest extends BrowserKitTest
'auth.method' => 'ldap',
'services.ldap.base_dn' => 'dc=ldap,dc=local',
'services.ldap.email_attribute' => 'mail',
'services.ldap.display_name_attribute' => 'cn',
'services.ldap.user_to_groups' => false,
'auth.providers.users.driver' => 'ldap',
]);
@@ -45,6 +46,15 @@ class LdapTest extends BrowserKitTest
});
}
protected function mockUserLogin()
{
return $this->visit('/login')
->see('Username')
->type($this->mockUser->name, '#username')
->type($this->mockUser->password, '#password')
->press('Log In');
}
public function test_login()
{
$this->mockLdap->shouldReceive('connect')->once()->andReturn($this->resourceId);
@@ -60,11 +70,7 @@ class LdapTest extends BrowserKitTest
$this->mockLdap->shouldReceive('bind')->times(6)->andReturn(true);
$this->mockEscapes(4);
$this->visit('/login')
->see('Username')
->type($this->mockUser->name, '#username')
->type($this->mockUser->password, '#password')
->press('Log In')
$this->mockUserLogin()
->seePageIs('/login')->see('Please enter an email to use for this account.');
$this->type($this->mockUser->email, '#email')
@@ -90,11 +96,7 @@ class LdapTest extends BrowserKitTest
$this->mockLdap->shouldReceive('bind')->times(3)->andReturn(true);
$this->mockEscapes(2);
$this->visit('/login')
->see('Username')
->type($this->mockUser->name, '#username')
->type($this->mockUser->password, '#password')
->press('Log In')
$this->mockUserLogin()
->seePageIs('/')
->see($this->mockUser->name)
->seeInDatabase('users', ['email' => $this->mockUser->email, 'email_confirmed' => false, 'external_auth_id' => $ldapDn]);
@@ -115,11 +117,7 @@ class LdapTest extends BrowserKitTest
$this->mockLdap->shouldReceive('bind')->times(3)->andReturn(true, true, false);
$this->mockEscapes(2);
$this->visit('/login')
->see('Username')
->type($this->mockUser->name, '#username')
->type($this->mockUser->password, '#password')
->press('Log In')
$this->mockUserLogin()
->seePageIs('/login')->see('These credentials do not match our records.')
->dontSeeInDatabase('users', ['external_auth_id' => $this->mockUser->name]);
}
@@ -196,12 +194,7 @@ class LdapTest extends BrowserKitTest
$this->mockEscapes(5);
$this->mockExplodes(6);
$this->visit('/login')
->see('Username')
->type($this->mockUser->name, '#username')
->type($this->mockUser->password, '#password')
->press('Log In')
->seePageIs('/');
$this->mockUserLogin()->seePageIs('/');
$user = User::where('email', $this->mockUser->email)->first();
$this->seeInDatabase('role_user', [
@@ -249,12 +242,7 @@ class LdapTest extends BrowserKitTest
$this->mockEscapes(4);
$this->mockExplodes(2);
$this->visit('/login')
->see('Username')
->type($this->mockUser->name, '#username')
->type($this->mockUser->password, '#password')
->press('Log In')
->seePageIs('/');
$this->mockUserLogin()->seePageIs('/');
$user = User::where('email', $this->mockUser->email)->first();
$this->seeInDatabase('role_user', [
@@ -303,12 +291,7 @@ class LdapTest extends BrowserKitTest
$this->mockEscapes(4);
$this->mockExplodes(2);
$this->visit('/login')
->see('Username')
->type($this->mockUser->name, '#username')
->type($this->mockUser->password, '#password')
->press('Log In')
->seePageIs('/');
$this->mockUserLogin()->seePageIs('/');
$user = User::where('email', $this->mockUser->email)->first();
$this->seeInDatabase('role_user', [
@@ -354,12 +337,7 @@ class LdapTest extends BrowserKitTest
$this->mockEscapes(5);
$this->mockExplodes(6);
$this->visit('/login')
->see('Username')
->type($this->mockUser->name, '#username')
->type($this->mockUser->password, '#password')
->press('Log In')
->seePageIs('/');
$this->mockUserLogin()->seePageIs('/');
$user = User::where('email', $this->mockUser->email)->first();
$this->seeInDatabase('role_user', [
@@ -372,4 +350,63 @@ class LdapTest extends BrowserKitTest
]);
}
public function test_login_uses_specified_display_name_attribute()
{
app('config')->set([
'services.ldap.display_name_attribute' => 'displayName'
]);
$this->mockLdap->shouldReceive('connect')->once()->andReturn($this->resourceId);
$this->mockLdap->shouldReceive('setVersion')->once();
$this->mockLdap->shouldReceive('setOption')->times(4);
$this->mockLdap->shouldReceive('searchAndGetEntries')->times(4)
->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
->andReturn(['count' => 1, 0 => [
'uid' => [$this->mockUser->name],
'cn' => [$this->mockUser->name],
'dn' => ['dc=test' . config('services.ldap.base_dn')],
'displayName' => 'displayNameAttribute'
]]);
$this->mockLdap->shouldReceive('bind')->times(6)->andReturn(true);
$this->mockEscapes(4);
$this->mockUserLogin()
->seePageIs('/login')->see('Please enter an email to use for this account.');
$this->type($this->mockUser->email, '#email')
->press('Log In')
->seePageIs('/')
->see('displayNameAttribute')
->seeInDatabase('users', ['email' => $this->mockUser->email, 'email_confirmed' => false, 'external_auth_id' => $this->mockUser->name, 'name' => 'displayNameAttribute']);
}
public function test_login_uses_default_display_name_attribute_if_specified_not_present()
{
app('config')->set([
'services.ldap.display_name_attribute' => 'displayName'
]);
$this->mockLdap->shouldReceive('connect')->once()->andReturn($this->resourceId);
$this->mockLdap->shouldReceive('setVersion')->once();
$this->mockLdap->shouldReceive('setOption')->times(4);
$this->mockLdap->shouldReceive('searchAndGetEntries')->times(4)
->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
->andReturn(['count' => 1, 0 => [
'uid' => [$this->mockUser->name],
'cn' => [$this->mockUser->name],
'dn' => ['dc=test' . config('services.ldap.base_dn')]
]]);
$this->mockLdap->shouldReceive('bind')->times(6)->andReturn(true);
$this->mockEscapes(4);
$this->mockUserLogin()
->seePageIs('/login')->see('Please enter an email to use for this account.');
$this->type($this->mockUser->email, '#email')
->press('Log In')
->seePageIs('/')
->see($this->mockUser->name)
->seeInDatabase('users', ['email' => $this->mockUser->email, 'email_confirmed' => false, 'external_auth_id' => $this->mockUser->name, 'name' => $this->mockUser->name]);
}
}

View File

@@ -1,5 +1,7 @@
<?php namespace Tests;
use BookStack\Auth\Role;
use BookStack\Auth\User;
use BookStack\Entities\Book;
use BookStack\Entities\Bookshelf;
@@ -27,6 +29,22 @@ class BookShelfTest extends TestCase
$resp->assertElementContains('header', 'Shelves');
}
public function test_shelves_shows_in_header_if_have_any_shelve_view_permission()
{
$user = factory(User::class)->create();
$this->giveUserPermissions($user, ['image-create-all']);
$shelf = Bookshelf::first();
$userRole = $user->roles()->first();
$resp = $this->actingAs($user)->get('/');
$resp->assertElementNotContains('header', 'Shelves');
$this->setEntityRestrictions($shelf, ['view'], [$userRole]);
$resp = $this->get('/');
$resp->assertElementContains('header', 'Shelves');
}
public function test_shelves_page_contains_create_link()
{
$resp = $this->asEditor()->get('/shelves');

View File

@@ -1,5 +1,6 @@
<?php namespace Tests;
use BookStack\Auth\Role;
use BookStack\Entities\Book;
use BookStack\Entities\Chapter;
use BookStack\Entities\Page;
@@ -239,4 +240,35 @@ class SortTest extends TestCase
$this->assertTrue($pageCopy->id !== $page->id, 'Page copy is not the same instance');
}
public function test_page_can_be_copied_without_edit_permission()
{
$page = Page::first();
$currentBook = $page->book;
$newBook = Book::where('id', '!=', $currentBook->id)->first();
$viewer = $this->getViewer();
$resp = $this->actingAs($viewer)->get($page->getUrl());
$resp->assertDontSee($page->getUrl('/copy'));
$newBook->created_by = $viewer->id;
$newBook->save();
$this->giveUserPermissions($viewer, ['page-create-own']);
$this->regenEntityPermissions($newBook);
$resp = $this->actingAs($viewer)->get($page->getUrl());
$resp->assertSee($page->getUrl('/copy'));
$movePageResp = $this->post($page->getUrl('/copy'), [
'entity_selection' => 'book:' . $newBook->id,
'name' => 'My copied test page'
]);
$movePageResp->assertRedirect();
$this->assertDatabaseHas('pages', [
'name' => 'My copied test page',
'created_by' => $viewer->id,
'book_id' => $newBook->id,
]);
}
}

View File

@@ -28,16 +28,6 @@ class AttachmentTest extends TestCase
return $this->call('POST', '/attachments/upload', ['uploaded_to' => $uploadedTo], [], ['file' => $file], []);
}
/**
* Get the expected upload path for a file.
* @param $fileName
* @return string
*/
protected function getUploadPath($fileName)
{
return 'uploads/files/' . Date('Y-m-M') . '/' . $fileName;
}
/**
* Delete all uploaded files.
* To assist with cleanup.
@@ -64,17 +54,34 @@ class AttachmentTest extends TestCase
'order' => 1,
'created_by' => $admin->id,
'updated_by' => $admin->id,
'path' => $this->getUploadPath($fileName)
];
$upload = $this->uploadFile($fileName, $page->id);
$upload->assertStatus(200);
$attachment = Attachment::query()->orderBy('id', 'desc')->first();
$expectedResp['path'] = $attachment->path;
$upload->assertJson($expectedResp);
$this->assertDatabaseHas('attachments', $expectedResp);
$this->deleteUploads();
}
public function test_file_upload_does_not_use_filename()
{
$page = Page::first();
$fileName = 'upload_test_file.txt';
$upload = $this->asAdmin()->uploadFile($fileName, $page->id);
$upload->assertStatus(200);
$attachment = Attachment::query()->orderBy('id', 'desc')->first();
$this->assertNotContains($fileName, $attachment->path);
$this->assertStringEndsWith('.txt', $attachment->path);
}
public function test_file_display_and_access()
{
$page = Page::first();
@@ -172,7 +179,8 @@ class AttachmentTest extends TestCase
$fileName = 'deletion_test.txt';
$this->uploadFile($fileName, $page->id);
$filePath = base_path('storage/' . $this->getUploadPath($fileName));
$attachment = Attachment::query()->orderBy('id', 'desc')->first();
$filePath = storage_path($attachment->path);
$this->assertTrue(file_exists($filePath), 'File at path ' . $filePath . ' does not exist');
$attachment = \BookStack\Uploads\Attachment::first();
@@ -193,7 +201,8 @@ class AttachmentTest extends TestCase
$fileName = 'deletion_test.txt';
$this->uploadFile($fileName, $page->id);
$filePath = base_path('storage/' . $this->getUploadPath($fileName));
$attachment = Attachment::query()->orderBy('id', 'desc')->first();
$filePath = storage_path($attachment->path);
$this->assertTrue(file_exists($filePath), 'File at path ' . $filePath . ' does not exist');
$this->assertDatabaseHas('attachments', [

View File

@@ -39,13 +39,69 @@ class ImageTest extends TestCase
]);
}
public function test_php_files_cannot_be_uploaded()
{
$page = Page::first();
$admin = $this->getAdmin();
$this->actingAs($admin);
$fileName = 'bad.php';
$relPath = $this->getTestImagePath('gallery', $fileName);
$this->deleteImage($relPath);
$file = $this->getTestImage($fileName);
$upload = $this->withHeader('Content-Type', 'image/jpeg')->call('POST', '/images/gallery/upload', ['uploaded_to' => $page->id], [], ['file' => $file], []);
$upload->assertStatus(302);
$this->assertFalse(file_exists(public_path($relPath)), 'Uploaded php file was uploaded but should have been stopped');
$this->assertDatabaseMissing('images', [
'type' => 'gallery',
'name' => $fileName
]);
}
public function test_php_like_files_cannot_be_uploaded()
{
$page = Page::first();
$admin = $this->getAdmin();
$this->actingAs($admin);
$fileName = 'bad.phtml';
$relPath = $this->getTestImagePath('gallery', $fileName);
$this->deleteImage($relPath);
$file = $this->getTestImage($fileName);
$upload = $this->withHeader('Content-Type', 'image/jpeg')->call('POST', '/images/gallery/upload', ['uploaded_to' => $page->id], [], ['file' => $file], []);
$upload->assertStatus(302);
$this->assertFalse(file_exists(public_path($relPath)), 'Uploaded php file was uploaded but should have been stopped');
}
public function test_files_with_double_extensions_cannot_be_uploaded()
{
$page = Page::first();
$admin = $this->getAdmin();
$this->actingAs($admin);
$fileName = 'bad.phtml.png';
$relPath = $this->getTestImagePath('gallery', $fileName);
$this->deleteImage($relPath);
$file = $this->getTestImage($fileName);
$upload = $this->withHeader('Content-Type', 'image/png')->call('POST', '/images/gallery/upload', ['uploaded_to' => $page->id], [], ['file' => $file], []);
$upload->assertStatus(302);
$this->assertFalse(file_exists(public_path($relPath)), 'Uploaded double extension file was uploaded but should have been stopped');
}
public function test_secure_images_uploads_to_correct_place()
{
config()->set('filesystems.default', 'local_secure');
$this->asEditor();
$galleryFile = $this->getTestImage('my-secure-test-upload');
$galleryFile = $this->getTestImage('my-secure-test-upload.png');
$page = Page::first();
$expectedPath = storage_path('uploads/images/gallery/' . Date('Y-m-M') . '/my-secure-test-upload');
$expectedPath = storage_path('uploads/images/gallery/' . Date('Y-m-M') . '/my-secure-test-upload.png');
$upload = $this->call('POST', '/images/gallery/upload', ['uploaded_to' => $page->id], [], ['file' => $galleryFile], []);
$upload->assertStatus(200);
@@ -61,9 +117,9 @@ class ImageTest extends TestCase
{
config()->set('filesystems.default', 'local_secure');
$this->asEditor();
$galleryFile = $this->getTestImage('my-secure-test-upload');
$galleryFile = $this->getTestImage('my-secure-test-upload.png');
$page = Page::first();
$expectedPath = storage_path('uploads/images/gallery/' . Date('Y-m-M') . '/my-secure-test-upload');
$expectedPath = storage_path('uploads/images/gallery/' . Date('Y-m-M') . '/my-secure-test-upload.png');
$upload = $this->call('POST', '/images/gallery/upload', ['uploaded_to' => $page->id], [], ['file' => $galleryFile], []);
$imageUrl = json_decode($upload->getContent(), true)['url'];
@@ -84,9 +140,9 @@ class ImageTest extends TestCase
{
config()->set('filesystems.default', 'local_secure');
$this->asEditor();
$galleryFile = $this->getTestImage('my-system-test-upload');
$galleryFile = $this->getTestImage('my-system-test-upload.png');
$page = Page::first();
$expectedPath = public_path('uploads/images/system/' . Date('Y-m-M') . '/my-system-test-upload');
$expectedPath = public_path('uploads/images/system/' . Date('Y-m-M') . '/my-system-test-upload.png');
$upload = $this->call('POST', '/images/system/upload', ['uploaded_to' => $page->id], [], ['file' => $galleryFile], []);
$upload->assertStatus(200);

View File

@@ -1,6 +1,8 @@
<?php namespace Tests\Uploads;
use Illuminate\Http\UploadedFile;
trait UsesImages
{
/**
@@ -15,11 +17,11 @@ trait UsesImages
/**
* Get a test image that can be uploaded
* @param $fileName
* @return \Illuminate\Http\UploadedFile
* @return UploadedFile
*/
protected function getTestImage($fileName)
{
return new \Illuminate\Http\UploadedFile($this->getTestImageFilePath(), $fileName, 'image/png', 5238);
return new UploadedFile($this->getTestImageFilePath(), $fileName, 'image/png', 5238, null, true);
}
/**
@@ -46,12 +48,14 @@ trait UsesImages
* Uploads an image with the given name.
* @param $name
* @param int $uploadedTo
* @param string $contentType
* @return \Illuminate\Foundation\Testing\TestResponse
*/
protected function uploadImage($name, $uploadedTo = 0)
protected function uploadImage($name, $uploadedTo = 0, $contentType = 'image/png')
{
$file = $this->getTestImage($name);
return $this->call('POST', '/images/gallery/upload', ['uploaded_to' => $uploadedTo], [], ['file' => $file], []);
return $this->withHeader('Content-Type', $contentType)
->call('POST', '/images/gallery/upload', ['uploaded_to' => $uploadedTo], [], ['file' => $file], []);
}
/**

BIN
tests/test-data/bad.php Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 560 B

BIN
tests/test-data/bad.phtml Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 560 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 B

View File

@@ -1 +1 @@
v0.25.1
v0.25.5