mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
Merge remote-tracking branch 'origin' into bookshelves
This commit is contained in:
commit
b5a2d3c1c4
2
.browserslistrc
Normal file
2
.browserslistrc
Normal file
@ -0,0 +1,2 @@
|
||||
>0.25%
|
||||
not op_mini all
|
@ -56,6 +56,8 @@ TWITCH_APP_SECRET=false
|
||||
GITLAB_APP_ID=false
|
||||
GITLAB_APP_SECRET=false
|
||||
GITLAB_BASE_URI=false
|
||||
DISCORD_APP_ID=false
|
||||
DISCORD_APP_SECRET=false
|
||||
|
||||
# External services such as Gravatar and Draw.IO
|
||||
DISABLE_EXTERNAL_SERVICES=false
|
||||
@ -67,6 +69,13 @@ LDAP_DN=false
|
||||
LDAP_PASS=false
|
||||
LDAP_USER_FILTER=false
|
||||
LDAP_VERSION=false
|
||||
# Do you want to sync LDAP groups to BookStack roles for a user
|
||||
LDAP_USER_TO_GROUPS=false
|
||||
# What is the LDAP attribute for group memberships
|
||||
LDAP_GROUP_ATTRIBUTE="memberOf"
|
||||
# Would you like to remove users from roles on BookStack if they do not match on LDAP
|
||||
# If false, the ldap groups-roles sync will only add users to roles
|
||||
LDAP_REMOVE_FROM_GROUPS=false
|
||||
|
||||
# Mail settings
|
||||
MAIL_DRIVER=smtp
|
||||
|
21
.github/ISSUE_TEMPLATE.md
vendored
21
.github/ISSUE_TEMPLATE.md
vendored
@ -1,21 +0,0 @@
|
||||
### For Feature Requests
|
||||
|
||||
Desired Feature:
|
||||
|
||||
### For Bug Reports
|
||||
|
||||
* BookStack Version *(Found in settings, Please don't put 'latest')*:
|
||||
* PHP Version:
|
||||
* MySQL Version:
|
||||
|
||||
##### Expected Behavior
|
||||
|
||||
|
||||
|
||||
##### Current Behavior
|
||||
|
||||
|
||||
|
||||
##### Steps to Reproduce
|
||||
|
||||
|
29
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
29
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Steps To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Your Configuration (please complete the following information):**
|
||||
- Exact BookStack Version (Found in settings):
|
||||
- PHP Version:
|
||||
- Using Docker or reverse proxy (Yes/No):
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
14
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
14
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
|
||||
---
|
||||
|
||||
**Describe the feature you'd like**
|
||||
A clear description of the feature you'd like implemented in BookStack.
|
||||
|
||||
**Describe the benefits this feature would bring to BookStack users**
|
||||
Explain the measurable benefits this feature would achieve.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
@ -5,6 +5,7 @@ namespace BookStack\Http\Controllers\Auth;
|
||||
use BookStack\Exceptions\AuthException;
|
||||
use BookStack\Http\Controllers\Controller;
|
||||
use BookStack\Repos\UserRepo;
|
||||
use BookStack\Services\LdapService;
|
||||
use BookStack\Services\SocialAuthService;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
||||
@ -36,18 +37,21 @@ class LoginController extends Controller
|
||||
protected $redirectAfterLogout = '/login';
|
||||
|
||||
protected $socialAuthService;
|
||||
protected $ldapService;
|
||||
protected $userRepo;
|
||||
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
*
|
||||
* @param SocialAuthService $socialAuthService
|
||||
* @param LdapService $ldapService
|
||||
* @param UserRepo $userRepo
|
||||
*/
|
||||
public function __construct(SocialAuthService $socialAuthService, UserRepo $userRepo)
|
||||
public function __construct(SocialAuthService $socialAuthService, LdapService $ldapService, UserRepo $userRepo)
|
||||
{
|
||||
$this->middleware('guest', ['only' => ['getLogin', 'postLogin']]);
|
||||
$this->socialAuthService = $socialAuthService;
|
||||
$this->ldapService = $ldapService;
|
||||
$this->userRepo = $userRepo;
|
||||
$this->redirectPath = baseUrl('/');
|
||||
$this->redirectAfterLogout = baseUrl('/login');
|
||||
@ -96,6 +100,11 @@ class LoginController extends Controller
|
||||
auth()->login($user);
|
||||
}
|
||||
|
||||
// Sync LDAP groups if required
|
||||
if ($this->ldapService->shouldSyncGroups()) {
|
||||
$this->ldapService->syncGroups($user);
|
||||
}
|
||||
|
||||
$path = session()->pull('url.intended', '/');
|
||||
$path = baseUrl($path, true);
|
||||
return redirect($path);
|
||||
@ -125,6 +134,7 @@ class LoginController extends Controller
|
||||
* Redirect to the relevant social site.
|
||||
* @param $socialDriver
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse
|
||||
* @throws \BookStack\Exceptions\SocialDriverNotConfigured
|
||||
*/
|
||||
public function getSocialLogin($socialDriver)
|
||||
{
|
||||
|
@ -5,7 +5,6 @@ use BookStack\Exceptions\NotFoundException;
|
||||
use BookStack\Repos\EntityRepo;
|
||||
use BookStack\Repos\UserRepo;
|
||||
use BookStack\Services\ExportService;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Views;
|
||||
@ -38,11 +37,18 @@ class PageController extends Controller
|
||||
* @param string $chapterSlug
|
||||
* @return Response
|
||||
* @internal param bool $pageSlug
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function create($bookSlug, $chapterSlug = null)
|
||||
{
|
||||
$book = $this->entityRepo->getBySlug('book', $bookSlug);
|
||||
$chapter = $chapterSlug ? $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug) : null;
|
||||
if ($chapterSlug !== null) {
|
||||
$chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
|
||||
$book = $chapter->book;
|
||||
} else {
|
||||
$chapter = null;
|
||||
$book = $this->entityRepo->getBySlug('book', $bookSlug);
|
||||
}
|
||||
|
||||
$parent = $chapter ? $chapter : $book;
|
||||
$this->checkOwnablePermission('page-create', $parent);
|
||||
|
||||
@ -52,7 +58,7 @@ class PageController extends Controller
|
||||
return redirect($draft->getUrl());
|
||||
}
|
||||
|
||||
// Otherwise show edit view
|
||||
// Otherwise show the edit view if they're a guest
|
||||
$this->setPageTitle(trans('entities.pages_new'));
|
||||
return view('pages/guest-create', ['parent' => $parent]);
|
||||
}
|
||||
@ -71,8 +77,14 @@ class PageController extends Controller
|
||||
'name' => 'required|string|max:255'
|
||||
]);
|
||||
|
||||
$book = $this->entityRepo->getBySlug('book', $bookSlug);
|
||||
$chapter = $chapterSlug ? $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug) : null;
|
||||
if ($chapterSlug !== null) {
|
||||
$chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
|
||||
$book = $chapter->book;
|
||||
} else {
|
||||
$chapter = null;
|
||||
$book = $this->entityRepo->getBySlug('book', $bookSlug);
|
||||
}
|
||||
|
||||
$parent = $chapter ? $chapter : $book;
|
||||
$this->checkOwnablePermission('page-create', $parent);
|
||||
|
||||
@ -93,7 +105,7 @@ class PageController extends Controller
|
||||
public function editDraft($bookSlug, $pageId)
|
||||
{
|
||||
$draft = $this->entityRepo->getById('page', $pageId, true);
|
||||
$this->checkOwnablePermission('page-create', $draft->book);
|
||||
$this->checkOwnablePermission('page-create', $draft->parent);
|
||||
$this->setPageTitle(trans('entities.pages_edit_draft'));
|
||||
|
||||
$draftsEnabled = $this->signedIn;
|
||||
@ -119,12 +131,10 @@ class PageController extends Controller
|
||||
]);
|
||||
|
||||
$input = $request->all();
|
||||
$book = $this->entityRepo->getBySlug('book', $bookSlug);
|
||||
|
||||
$draftPage = $this->entityRepo->getById('page', $pageId, true);
|
||||
$book = $draftPage->book;
|
||||
|
||||
$chapterId = intval($draftPage->chapter_id);
|
||||
$parent = $chapterId !== 0 ? $this->entityRepo->getById('chapter', $chapterId) : $book;
|
||||
$parent = $draftPage->parent;
|
||||
$this->checkOwnablePermission('page-create', $parent);
|
||||
|
||||
if ($parent->isA('chapter')) {
|
||||
|
@ -78,6 +78,7 @@ class PermissionController extends Controller
|
||||
* @param $id
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
* @throws PermissionsException
|
||||
*/
|
||||
public function updateRole($id, Request $request)
|
||||
{
|
||||
|
@ -28,6 +28,15 @@ class Page extends Entity
|
||||
return $this->belongsTo(Book::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent item
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function parent()
|
||||
{
|
||||
return $this->chapter_id ? $this->chapter() : $this->book();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the chapter that this page is in, If applicable.
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
|
@ -20,6 +20,7 @@ class EventServiceProvider extends ServiceProvider
|
||||
'SocialiteProviders\Okta\OktaExtendSocialite@handle',
|
||||
'SocialiteProviders\GitLab\GitLabExtendSocialite@handle',
|
||||
'SocialiteProviders\Twitch\TwitchExtendSocialite@handle',
|
||||
'SocialiteProviders\Discord\DiscordExtendSocialite@handle',
|
||||
],
|
||||
];
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
class Role extends Model
|
||||
{
|
||||
|
||||
protected $fillable = ['display_name', 'description'];
|
||||
protected $fillable = ['display_name', 'description', 'external_auth_id'];
|
||||
|
||||
/**
|
||||
* The roles that belong to the role.
|
||||
|
@ -1,7 +1,11 @@
|
||||
<?php namespace BookStack\Services;
|
||||
|
||||
use BookStack\Exceptions\LdapException;
|
||||
use BookStack\Repos\UserRepo;
|
||||
use BookStack\Role;
|
||||
use BookStack\User;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
/**
|
||||
* Class LdapService
|
||||
@ -14,15 +18,55 @@ class LdapService
|
||||
protected $ldap;
|
||||
protected $ldapConnection;
|
||||
protected $config;
|
||||
protected $userRepo;
|
||||
protected $enabled;
|
||||
|
||||
/**
|
||||
* LdapService constructor.
|
||||
* @param Ldap $ldap
|
||||
* @param UserRepo $userRepo
|
||||
*/
|
||||
public function __construct(Ldap $ldap)
|
||||
public function __construct(Ldap $ldap, UserRepo $userRepo)
|
||||
{
|
||||
$this->ldap = $ldap;
|
||||
$this->config = config('services.ldap');
|
||||
$this->userRepo = $userRepo;
|
||||
$this->enabled = config('auth.method') === 'ldap';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if groups should be synced.
|
||||
* @return bool
|
||||
*/
|
||||
public function shouldSyncGroups()
|
||||
{
|
||||
return $this->enabled && $this->config['user_to_groups'] !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for attributes for a specific user on the ldap
|
||||
* @param string $userName
|
||||
* @param array $attributes
|
||||
* @return null|array
|
||||
* @throws LdapException
|
||||
*/
|
||||
private function getUserWithAttributes($userName, $attributes)
|
||||
{
|
||||
$ldapConnection = $this->getConnection();
|
||||
$this->bindSystemUser($ldapConnection);
|
||||
|
||||
// Find user
|
||||
$userFilter = $this->buildFilter($this->config['user_filter'], ['user' => $userName]);
|
||||
$baseDn = $this->config['base_dn'];
|
||||
|
||||
$followReferrals = $this->config['follow_referrals'] ? 1 : 0;
|
||||
$this->ldap->setOption($ldapConnection, LDAP_OPT_REFERRALS, $followReferrals);
|
||||
$users = $this->ldap->searchAndGetEntries($ldapConnection, $baseDn, $userFilter, $attributes);
|
||||
if ($users['count'] === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $users[0];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -34,21 +78,13 @@ class LdapService
|
||||
*/
|
||||
public function getUserDetails($userName)
|
||||
{
|
||||
$ldapConnection = $this->getConnection();
|
||||
$this->bindSystemUser($ldapConnection);
|
||||
|
||||
// Find user
|
||||
$userFilter = $this->buildFilter($this->config['user_filter'], ['user' => $userName]);
|
||||
$baseDn = $this->config['base_dn'];
|
||||
$emailAttr = $this->config['email_attribute'];
|
||||
$followReferrals = $this->config['follow_referrals'] ? 1 : 0;
|
||||
$this->ldap->setOption($ldapConnection, LDAP_OPT_REFERRALS, $followReferrals);
|
||||
$users = $this->ldap->searchAndGetEntries($ldapConnection, $baseDn, $userFilter, ['cn', 'uid', 'dn', $emailAttr]);
|
||||
if ($users['count'] === 0) {
|
||||
$user = $this->getUserWithAttributes($userName, ['cn', 'uid', 'dn', $emailAttr]);
|
||||
|
||||
if ($user === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$user = $users[0];
|
||||
return [
|
||||
'uid' => (isset($user['uid'])) ? $user['uid'][0] : $user['dn'],
|
||||
'name' => $user['cn'][0],
|
||||
@ -162,4 +198,172 @@ class LdapService
|
||||
}
|
||||
return strtr($filterString, $newAttrs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the groups a user is a part of on ldap
|
||||
* @param string $userName
|
||||
* @return array|null
|
||||
* @throws LdapException
|
||||
*/
|
||||
public function getUserGroups($userName)
|
||||
{
|
||||
$groupsAttr = $this->config['group_attribute'];
|
||||
$user = $this->getUserWithAttributes($userName, [$groupsAttr]);
|
||||
|
||||
if ($user === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$userGroups = $this->groupFilter($user);
|
||||
$userGroups = $this->getGroupsRecursive($userGroups, []);
|
||||
return $userGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent groups of an array of groups
|
||||
* @param array $groupsArray
|
||||
* @param array $checked
|
||||
* @return array
|
||||
* @throws LdapException
|
||||
*/
|
||||
private function getGroupsRecursive($groupsArray, $checked)
|
||||
{
|
||||
$groups_to_add = [];
|
||||
foreach ($groupsArray as $groupName) {
|
||||
if (in_array($groupName, $checked)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$groupsToAdd = $this->getGroupGroups($groupName);
|
||||
$groups_to_add = array_merge($groups_to_add, $groupsToAdd);
|
||||
$checked[] = $groupName;
|
||||
}
|
||||
$groupsArray = array_unique(array_merge($groupsArray, $groups_to_add), SORT_REGULAR);
|
||||
|
||||
if (!empty($groups_to_add)) {
|
||||
return $this->getGroupsRecursive($groupsArray, $checked);
|
||||
} else {
|
||||
return $groupsArray;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent groups of a single group
|
||||
* @param string $groupName
|
||||
* @return array
|
||||
* @throws LdapException
|
||||
*/
|
||||
private function getGroupGroups($groupName)
|
||||
{
|
||||
$ldapConnection = $this->getConnection();
|
||||
$this->bindSystemUser($ldapConnection);
|
||||
|
||||
$followReferrals = $this->config['follow_referrals'] ? 1 : 0;
|
||||
$this->ldap->setOption($ldapConnection, LDAP_OPT_REFERRALS, $followReferrals);
|
||||
|
||||
$baseDn = $this->config['base_dn'];
|
||||
$groupsAttr = strtolower($this->config['group_attribute']);
|
||||
|
||||
$groups = $this->ldap->searchAndGetEntries($ldapConnection, $baseDn, 'CN='.$groupName, [$groupsAttr]);
|
||||
if ($groups['count'] === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$groupGroups = $this->groupFilter($groups[0]);
|
||||
return $groupGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter out LDAP CN and DN language in a ldap search return
|
||||
* Gets the base CN (common name) of the string
|
||||
* @param string $ldapSearchReturn
|
||||
* @return array
|
||||
*/
|
||||
protected function groupFilter($ldapSearchReturn)
|
||||
{
|
||||
$groupsAttr = strtolower($this->config['group_attribute']);
|
||||
$ldapGroups = [];
|
||||
$count = 0;
|
||||
if (isset($ldapSearchReturn[$groupsAttr]['count'])) {
|
||||
$count = (int) $ldapSearchReturn[$groupsAttr]['count'];
|
||||
}
|
||||
for ($i=0; $i<$count; $i++) {
|
||||
$dnComponents = ldap_explode_dn($ldapSearchReturn[$groupsAttr][$i], 1);
|
||||
if (!in_array($dnComponents[0], $ldapGroups)) {
|
||||
$ldapGroups[] = $dnComponents[0];
|
||||
}
|
||||
}
|
||||
return $ldapGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync the LDAP groups to the user roles for the current user
|
||||
* @param \BookStack\User $user
|
||||
* @throws LdapException
|
||||
*/
|
||||
public function syncGroups(User $user)
|
||||
{
|
||||
$userLdapGroups = $this->getUserGroups($user->external_auth_id);
|
||||
|
||||
// Get the ids for the roles from the names
|
||||
$ldapGroupsAsRoles = $this->matchLdapGroupsToSystemsRoles($userLdapGroups);
|
||||
|
||||
// Sync groups
|
||||
if ($this->config['remove_from_groups']) {
|
||||
$user->roles()->sync($ldapGroupsAsRoles);
|
||||
$this->userRepo->attachDefaultRole($user);
|
||||
} else {
|
||||
$user->roles()->syncWithoutDetaching($ldapGroupsAsRoles);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Match an array of group names from LDAP to BookStack system roles.
|
||||
* Formats LDAP group names to be lower-case and hyphenated.
|
||||
* @param array $groupNames
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
protected function matchLdapGroupsToSystemsRoles(array $groupNames)
|
||||
{
|
||||
foreach ($groupNames as $i => $groupName) {
|
||||
$groupNames[$i] = str_replace(' ', '-', trim(strtolower($groupName)));
|
||||
}
|
||||
|
||||
$roles = Role::query()->where(function(Builder $query) use ($groupNames) {
|
||||
$query->whereIn('name', $groupNames);
|
||||
foreach ($groupNames as $groupName) {
|
||||
$query->orWhere('external_auth_id', 'LIKE', '%' . $groupName . '%');
|
||||
}
|
||||
})->get();
|
||||
|
||||
$matchedRoles = $roles->filter(function(Role $role) use ($groupNames) {
|
||||
return $this->roleMatchesGroupNames($role, $groupNames);
|
||||
});
|
||||
|
||||
return $matchedRoles->pluck('id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a role against an array of group names to see if it matches.
|
||||
* Checked against role 'external_auth_id' if set otherwise the name of the role.
|
||||
* @param Role $role
|
||||
* @param array $groupNames
|
||||
* @return bool
|
||||
*/
|
||||
protected function roleMatchesGroupNames(Role $role, array $groupNames)
|
||||
{
|
||||
if ($role->external_auth_id) {
|
||||
$externalAuthIds = explode(',', strtolower($role->external_auth_id));
|
||||
foreach ($externalAuthIds as $externalAuthId) {
|
||||
if (in_array(trim($externalAuthId), $groupNames)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
$roleName = str_replace(' ', '-', trim(strtolower($role->display_name)));
|
||||
return in_array($roleName, $groupNames);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ class SocialAuthService
|
||||
protected $socialite;
|
||||
protected $socialAccount;
|
||||
|
||||
protected $validSocialDrivers = ['google', 'github', 'facebook', 'slack', 'twitter', 'azure', 'okta', 'gitlab', 'twitch'];
|
||||
protected $validSocialDrivers = ['google', 'github', 'facebook', 'slack', 'twitter', 'azure', 'okta', 'gitlab', 'twitch', 'discord'];
|
||||
|
||||
/**
|
||||
* SocialAuthService constructor.
|
||||
|
@ -20,7 +20,8 @@
|
||||
"socialiteproviders/microsoft-azure": "^3.0",
|
||||
"socialiteproviders/okta": "^1.0",
|
||||
"socialiteproviders/gitlab": "^3.0",
|
||||
"socialiteproviders/twitch": "^3.0"
|
||||
"socialiteproviders/twitch": "^3.0",
|
||||
"socialiteproviders/discord": "^2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"filp/whoops": "~2.0",
|
||||
|
51
composer.lock
generated
51
composer.lock
generated
@ -1,10 +1,10 @@
|
||||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "3bf33ab103b15b06ca06c85fd8ae3b78",
|
||||
"content-hash": "b9ea2a42e2f7780b3a54d4b7327750e0",
|
||||
"packages": [
|
||||
{
|
||||
"name": "aws/aws-sdk-php",
|
||||
@ -1665,16 +1665,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phenx/php-svg-lib",
|
||||
"version": "v0.3",
|
||||
"version": "v0.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PhenX/php-svg-lib.git",
|
||||
"reference": "a85f7fe9fe08d093a4a8583cdd306b553ff918aa"
|
||||
"reference": "8f543ede60386faec9b0012833536de4b6083bb9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PhenX/php-svg-lib/zipball/a85f7fe9fe08d093a4a8583cdd306b553ff918aa",
|
||||
"reference": "a85f7fe9fe08d093a4a8583cdd306b553ff918aa",
|
||||
"url": "https://api.github.com/repos/PhenX/php-svg-lib/zipball/8f543ede60386faec9b0012833536de4b6083bb9",
|
||||
"reference": "8f543ede60386faec9b0012833536de4b6083bb9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1701,7 +1701,7 @@
|
||||
],
|
||||
"description": "A library to read, parse and export to PDF SVG files.",
|
||||
"homepage": "https://github.com/PhenX/php-svg-lib",
|
||||
"time": "2017-05-24T10:07:27+00:00"
|
||||
"time": "2018-04-14T14:36:18+00:00"
|
||||
},
|
||||
{
|
||||
"name": "predis/predis",
|
||||
@ -2071,6 +2071,43 @@
|
||||
],
|
||||
"time": "2016-07-19T19:14:21+00:00"
|
||||
},
|
||||
{
|
||||
"name": "socialiteproviders/discord",
|
||||
"version": "v2.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/SocialiteProviders/Discord.git",
|
||||
"reference": "f934ca3f4fa5ea915c1d20852b826e860aa64727"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/SocialiteProviders/Discord/zipball/f934ca3f4fa5ea915c1d20852b826e860aa64727",
|
||||
"reference": "f934ca3f4fa5ea915c1d20852b826e860aa64727",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^5.6 || ^7.0",
|
||||
"socialiteproviders/manager": "~2.0 || ~3.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"SocialiteProviders\\Discord\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christopher Eklund",
|
||||
"email": "eklundchristopher@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "Discord OAuth2 Provider for Laravel Socialite",
|
||||
"time": "2017-08-28T02:20:40+00:00"
|
||||
},
|
||||
{
|
||||
"name": "socialiteproviders/gitlab",
|
||||
"version": "v3.0.2",
|
||||
|
@ -108,6 +108,12 @@ return [
|
||||
'redirect' => env('APP_URL') . '/login/service/twitch/callback',
|
||||
'name' => 'Twitch',
|
||||
],
|
||||
'discord' => [
|
||||
'client_id' => env('DISCORD_APP_ID'),
|
||||
'client_secret' => env('DISCORD_APP_SECRET'),
|
||||
'redirect' => env('APP_URL') . '/login/service/discord/callback',
|
||||
'name' => 'Discord',
|
||||
],
|
||||
|
||||
'ldap' => [
|
||||
'server' => env('LDAP_SERVER', false),
|
||||
@ -118,6 +124,9 @@ return [
|
||||
'version' => env('LDAP_VERSION', false),
|
||||
'email_attribute' => env('LDAP_EMAIL_ATTRIBUTE', 'mail'),
|
||||
'follow_referrals' => env('LDAP_FOLLOW_REFERRALS', false),
|
||||
]
|
||||
'user_to_groups' => env('LDAP_USER_TO_GROUPS',false),
|
||||
'group_attribute' => env('LDAP_GROUP_ATTRIBUTE', 'memberOf'),
|
||||
'remove_from_groups' => env('LDAP_REMOVE_FROM_GROUPS',false),
|
||||
]
|
||||
|
||||
];
|
||||
|
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddRoleExternalAuthId extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('roles', function (Blueprint $table) {
|
||||
$table->string('external_auth_id', 200)->default('');
|
||||
$table->index('external_auth_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('roles', function (Blueprint $table) {
|
||||
$table->dropColumn('external_auth_id');
|
||||
});
|
||||
}
|
||||
}
|
6312
package-lock.json
generated
6312
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -11,20 +11,20 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.0.0-beta.40",
|
||||
"@babel/polyfill": "^7.0.0-beta.40",
|
||||
"@babel/preset-env": "^7.0.0-beta.40",
|
||||
"autoprefixer": "^8.1.0",
|
||||
"babel-loader": "^8.0.0-beta.0",
|
||||
"@babel/polyfill": "^7.0.0-beta.40",
|
||||
"css-loader": "^0.28.10",
|
||||
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
||||
"livereload": "^0.7.0",
|
||||
"node-sass": "^4.7.2",
|
||||
"node-sass": "^4.9.2",
|
||||
"npm-run-all": "^4.1.2",
|
||||
"postcss-loader": "^2.1.1",
|
||||
"sass-loader": "^7.0.1",
|
||||
"style-loader": "^0.21.0",
|
||||
"uglifyjs-webpack-plugin": "^1.2.3",
|
||||
"webpack": "^4.1.1",
|
||||
"webpack": "^4.16.3",
|
||||
"webpack-cli": "^2.0.11"
|
||||
},
|
||||
"dependencies": {
|
||||
|
1
resources/assets/icons/auth/discord.svg
Normal file
1
resources/assets/icons/auth/discord.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 245 240"><style>.st0{fill:#7289DA;}</style><path class="st0" d="M104.4 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1.1-6.1-4.5-11.1-10.2-11.1zM140.9 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1s-4.5-11.1-10.2-11.1z"/><path class="st0" d="M189.5 20h-134C44.2 20 35 29.2 35 40.6v135.2c0 11.4 9.2 20.6 20.5 20.6h113.4l-5.3-18.5 12.8 11.9 12.1 11.2 21.5 19V40.6c0-11.4-9.2-20.6-20.5-20.6zm-38.6 130.6s-3.6-4.3-6.6-8.1c13.1-3.7 18.1-11.9 18.1-11.9-4.1 2.7-8 4.6-11.5 5.9-5 2.1-9.8 3.5-14.5 4.3-9.6 1.8-18.4 1.3-25.9-.1-5.7-1.1-10.6-2.7-14.7-4.3-2.3-.9-4.8-2-7.3-3.4-.3-.2-.6-.3-.9-.5-.2-.1-.3-.2-.4-.3-1.8-1-2.8-1.7-2.8-1.7s4.8 8 17.5 11.8c-3 3.8-6.7 8.3-6.7 8.3-22.1-.7-30.5-15.2-30.5-15.2 0-32.2 14.4-58.3 14.4-58.3 14.4-10.8 28.1-10.5 28.1-10.5l1 1.2c-18 5.2-26.3 13.1-26.3 13.1s2.2-1.2 5.9-2.9c10.7-4.7 19.2-6 22.7-6.3.6-.1 1.1-.2 1.7-.2 6.1-.8 13-1 20.2-.2 9.5 1.1 19.7 3.9 30.1 9.6 0 0-7.9-7.5-24.9-12.7l1.4-1.6s13.7-.3 28.1 10.5c0 0 14.4 26.1 14.4 58.3 0 0-8.5 14.5-30.6 15.2z"/></svg>
|
After Width: | Height: | Size: 1.1 KiB |
@ -18,6 +18,13 @@ class MarkdownEditor {
|
||||
|
||||
this.onMarkdownScroll = this.onMarkdownScroll.bind(this);
|
||||
this.init();
|
||||
|
||||
// Scroll to text if needed.
|
||||
const queryParams = (new URL(window.location)).searchParams;
|
||||
const scrollText = queryParams.get('content-text');
|
||||
if (scrollText) {
|
||||
this.scrollToText(scrollText);
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
@ -387,6 +394,33 @@ class MarkdownEditor {
|
||||
});
|
||||
}
|
||||
|
||||
// Scroll to a specified text
|
||||
scrollToText(searchText) {
|
||||
if (!searchText) {
|
||||
return;
|
||||
}
|
||||
|
||||
const content = this.cm.getValue();
|
||||
const lines = content.split(/\r?\n/);
|
||||
let lineNumber = lines.findIndex(line => {
|
||||
return line && line.indexOf(searchText) !== -1;
|
||||
});
|
||||
|
||||
if (lineNumber === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.cm.scrollIntoView({
|
||||
line: lineNumber,
|
||||
}, 200);
|
||||
this.cm.focus();
|
||||
// set the cursor location.
|
||||
this.cm.setCursor({
|
||||
line: lineNumber,
|
||||
char: lines[lineNumber].length
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = MarkdownEditor ;
|
@ -6,11 +6,16 @@ class Notification {
|
||||
this.type = elem.getAttribute('notification');
|
||||
this.textElem = elem.querySelector('span');
|
||||
this.autohide = this.elem.hasAttribute('data-autohide');
|
||||
this.elem.style.display = 'grid';
|
||||
|
||||
window.$events.listen(this.type, text => {
|
||||
this.show(text);
|
||||
});
|
||||
elem.addEventListener('click', this.hide.bind(this));
|
||||
if (elem.hasAttribute('data-show')) this.show(this.textElem.textContent);
|
||||
|
||||
if (elem.hasAttribute('data-show')) {
|
||||
setTimeout(() => this.show(this.textElem.textContent), 100);
|
||||
}
|
||||
|
||||
this.hideCleanup = this.hideCleanup.bind(this);
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ class PageDisplay {
|
||||
|
||||
// Sidebar page nav click event
|
||||
$('.sidebar-page-nav').on('click', 'a', event => {
|
||||
goToText(event.target.getAttribute('href').substr(1));
|
||||
this.goToText(event.target.getAttribute('href').substr(1));
|
||||
});
|
||||
}
|
||||
|
||||
@ -74,11 +74,23 @@ class PageDisplay {
|
||||
pointerShowing = false;
|
||||
});
|
||||
|
||||
let updatePointerContent = () => {
|
||||
let updatePointerContent = ($elem) => {
|
||||
let inputText = pointerModeLink ? window.baseUrl(`/link/${this.pageId}#${pointerSectionId}`) : `{{@${this.pageId}#${pointerSectionId}}}`;
|
||||
if (pointerModeLink && inputText.indexOf('http') !== 0) inputText = window.location.protocol + "//" + window.location.host + inputText;
|
||||
|
||||
$pointer.find('input').val(inputText);
|
||||
|
||||
// update anchor if present
|
||||
const $editAnchor = $pointer.find('#pointer-edit');
|
||||
if ($editAnchor.length !== 0 && $elem) {
|
||||
const editHref = $editAnchor.data('editHref');
|
||||
const element = $elem[0];
|
||||
const elementId = element.id;
|
||||
|
||||
// get the first 50 characters.
|
||||
let queryContent = element.textContent && element.textContent.substring(0, 50);
|
||||
$editAnchor[0].href = `${editHref}?content-id=${elementId}&content-text=${encodeURIComponent(queryContent)}`;
|
||||
}
|
||||
};
|
||||
|
||||
// Show pointer when selecting a single block of tagged content
|
||||
@ -90,7 +102,7 @@ class PageDisplay {
|
||||
// Show pointer and set link
|
||||
let $elem = $(this);
|
||||
pointerSectionId = $elem.attr('id');
|
||||
updatePointerContent();
|
||||
updatePointerContent($elem);
|
||||
|
||||
$elem.before($pointer);
|
||||
$pointer.show();
|
||||
@ -219,7 +231,6 @@ class PageDisplay {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = PageDisplay;
|
||||
module.exports = PageDisplay;
|
||||
|
@ -483,13 +483,36 @@ class WysiwygEditor {
|
||||
},
|
||||
setup: function (editor) {
|
||||
|
||||
editor.on('init ExecCommand change input NodeChange ObjectResized', editorChange);
|
||||
editor.on('ExecCommand change input NodeChange ObjectResized', editorChange);
|
||||
|
||||
editor.on('init', () => {
|
||||
editorChange();
|
||||
// Scroll to the content if needed.
|
||||
const queryParams = (new URL(window.location)).searchParams;
|
||||
const scrollId = queryParams.get('content-id');
|
||||
if (scrollId) {
|
||||
scrollToText(scrollId);
|
||||
}
|
||||
});
|
||||
|
||||
function editorChange() {
|
||||
let content = editor.getContent();
|
||||
window.$events.emit('editor-html-change', content);
|
||||
}
|
||||
|
||||
function scrollToText(scrollId) {
|
||||
const element = editor.dom.get(encodeURIComponent(scrollId).replace(/!/g, '%21'));
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
// scroll the element into the view and put the cursor at the end.
|
||||
element.scrollIntoView();
|
||||
editor.selection.select(element, true);
|
||||
editor.selection.collapse(false);
|
||||
editor.focus();
|
||||
}
|
||||
|
||||
window.$events.listen('editor-html-update', html => {
|
||||
editor.setContent(html);
|
||||
editor.selection.select(editor.getBody(), true);
|
||||
|
@ -1,5 +1,4 @@
|
||||
// Global Polyfills
|
||||
import "@babel/polyfill"
|
||||
import "./services/dom-polyfills"
|
||||
|
||||
// Url retrieval function
|
||||
|
@ -12,4 +12,13 @@ export function utcTimeStampToLocalTime(timestamp) {
|
||||
let hours = date.getHours();
|
||||
let mins = date.getMinutes();
|
||||
return `${(hours>9?'':'0') + hours}:${(mins>9?'':'0') + mins}`;
|
||||
}
|
||||
|
||||
export function formatDateTime(date) {
|
||||
let month = date.getMonth() + 1;
|
||||
let day = date.getDate();
|
||||
let hours = date.getHours();
|
||||
let mins = date.getMinutes();
|
||||
|
||||
return `${date.getFullYear()}-${(month>9?'':'0') + month}-${(day>9?'':'0') + day} ${(hours>9?'':'0') + hours}:${(mins>9?'':'0') + mins}`;
|
||||
}
|
@ -52,7 +52,9 @@ let methods = {
|
||||
},
|
||||
|
||||
deleteFile(file) {
|
||||
if (!file.deleting) return file.deleting = true;
|
||||
if (!file.deleting) {
|
||||
return this.$set(file, 'deleting', true);
|
||||
}
|
||||
|
||||
this.$http.delete(window.baseUrl(`/attachments/${file.id}`)).then(resp => {
|
||||
this.$events.emit('success', resp.data.message);
|
||||
|
@ -1,3 +1,6 @@
|
||||
|
||||
import * as Dates from "../services/dates";
|
||||
|
||||
const dropzone = require('./components/dropzone');
|
||||
|
||||
let page = 0;
|
||||
@ -168,7 +171,7 @@ const methods = {
|
||||
},
|
||||
|
||||
getDate(stringDate) {
|
||||
return new Date(stringDate);
|
||||
return Dates.formatDateTime(new Date(stringDate));
|
||||
},
|
||||
|
||||
uploadSuccess(event) {
|
||||
|
@ -4,17 +4,18 @@
|
||||
top: 0;
|
||||
right: 0;
|
||||
margin: $-xl*2 $-xl;
|
||||
padding: $-l $-xl;
|
||||
padding: $-m $-l;
|
||||
background-color: #EEE;
|
||||
border-radius: 3px;
|
||||
box-shadow: $bs-med;
|
||||
box-shadow: $bs-card;
|
||||
z-index: 999999;
|
||||
cursor: pointer;
|
||||
max-width: 360px;
|
||||
transition: transform ease-in-out 280ms;
|
||||
transform: translate3d(580px, 0, 0);
|
||||
transform: translateX(580px);
|
||||
display: grid;
|
||||
grid-template-columns: 64px 1fr;
|
||||
grid-template-columns: 42px 1fr;
|
||||
color: #FFF;
|
||||
span, svg {
|
||||
vertical-align: middle;
|
||||
justify-self: center;
|
||||
@ -22,9 +23,9 @@
|
||||
}
|
||||
svg {
|
||||
fill: #EEEEEE;
|
||||
width: 4em;
|
||||
height: 4em;
|
||||
padding-right: $-m;
|
||||
width: 2.8rem;
|
||||
height: 2.8rem;
|
||||
padding-right: $-s;
|
||||
}
|
||||
span {
|
||||
vertical-align: middle;
|
||||
@ -32,18 +33,15 @@
|
||||
}
|
||||
&.pos {
|
||||
background-color: $positive;
|
||||
color: #EEE;
|
||||
}
|
||||
&.neg {
|
||||
background-color: $negative;
|
||||
color: #EEE;
|
||||
}
|
||||
&.warning {
|
||||
background-color: $secondary;
|
||||
color: #EEE;
|
||||
}
|
||||
&.showing {
|
||||
transform: translate3d(0, 0, 0);
|
||||
transform: translateX(0);
|
||||
}
|
||||
&.showing:hover {
|
||||
transform: translate3d(0, -2px, 0);
|
||||
|
@ -266,22 +266,13 @@ ul.pagination {
|
||||
padding: $-xxs $-s;
|
||||
border: 1px solid #CCC;
|
||||
margin-left: -1px;
|
||||
color: #888;
|
||||
fill: #888;
|
||||
user-select: none;
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
li.active span {
|
||||
background-color: rgba($primary, 0.8);
|
||||
color: #EEE;
|
||||
fill: #EEE;
|
||||
border-color: rgba($primary, 0.8);
|
||||
}
|
||||
a {
|
||||
color: $primary;
|
||||
fill: $primary;
|
||||
color: #FFF;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,6 +89,12 @@
|
||||
del {
|
||||
background: #FFECEC;
|
||||
}
|
||||
|
||||
&.page-revision {
|
||||
pre code {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Page content pointers
|
||||
@ -107,8 +113,13 @@
|
||||
position: absolute;
|
||||
top: -60px;
|
||||
background-color:#FFF;
|
||||
width: 272px;
|
||||
width: 275px;
|
||||
z-index: 55;
|
||||
|
||||
&.is-page-editable {
|
||||
width: 328px;
|
||||
}
|
||||
|
||||
&:before {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
@ -132,12 +143,13 @@
|
||||
width: 172px;
|
||||
z-index: 40;
|
||||
}
|
||||
input, button {
|
||||
input, button, a {
|
||||
position: relative;
|
||||
border-radius: 0;
|
||||
height: 28px;
|
||||
font-size: 12px;
|
||||
vertical-align: top;
|
||||
padding: 5px 16px;
|
||||
}
|
||||
> i {
|
||||
color: #888;
|
||||
@ -148,11 +160,22 @@
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
.button {
|
||||
.input-group .button {
|
||||
line-height: 1;
|
||||
margin: 0 0 0 -4px;
|
||||
box-shadow: none;
|
||||
}
|
||||
a.button {
|
||||
margin: 0 0 0 0;
|
||||
|
||||
&:hover {
|
||||
fill: #fff;
|
||||
}
|
||||
}
|
||||
.svg-icon {
|
||||
width: 1.2em;
|
||||
height: 1.2em;
|
||||
}
|
||||
}
|
||||
|
||||
// Attribute form
|
||||
|
@ -59,4 +59,5 @@ $text-light: #EEE;
|
||||
// Shadows
|
||||
$bs-light: 0 0 4px 1px #CCC;
|
||||
$bs-med: 0 1px 3px 1px rgba(76, 76, 76, 0.26);
|
||||
$bs-card: 0 1px 3px 1px rgba(76, 76, 76, 0.26), 0 1px 12px 0px rgba(76, 76, 76, 0.2);
|
||||
$bs-hover: 0 2px 2px 1px rgba(0,0,0,.13);
|
@ -8,34 +8,34 @@ return [
|
||||
*/
|
||||
|
||||
// Pages
|
||||
'page_create' => 'hat Seite erstellt:',
|
||||
'page_create_notification' => 'hat Seite erfolgreich erstellt:',
|
||||
'page_update' => 'hat Seite aktualisiert:',
|
||||
'page_update_notification' => 'hat Seite erfolgreich aktualisiert:',
|
||||
'page_delete' => 'hat Seite gelöscht:',
|
||||
'page_delete_notification' => 'hat Seite erfolgreich gelöscht:',
|
||||
'page_restore' => 'hat Seite wiederhergstellt:',
|
||||
'page_restore_notification' => 'hat Seite erfolgreich wiederhergstellt:',
|
||||
'page_move' => 'hat Seite verschoben:',
|
||||
'page_create' => 'Seite erstellt',
|
||||
'page_create_notification' => 'Die Seite wurde erfolgreich erstellt.',
|
||||
'page_update' => 'Seite aktualisiert',
|
||||
'page_update_notification' => 'Die Seite wurde erfolgreich aktualisiert.',
|
||||
'page_delete' => 'Seite gelöscht',
|
||||
'page_delete_notification' => 'Die Seite wurde erfolgreich gelöscht.',
|
||||
'page_restore' => 'Seite wiederhergstellt',
|
||||
'page_restore_notification' => 'Die Seite wurde erfolgreich wiederhergstellt.',
|
||||
'page_move' => 'Seite verschoben',
|
||||
|
||||
// Chapters
|
||||
'chapter_create' => 'hat Kapitel erstellt:',
|
||||
'chapter_create_notification' => 'hat Kapitel erfolgreich erstellt:',
|
||||
'chapter_update' => 'hat Kapitel aktualisiert:',
|
||||
'chapter_update_notification' => 'hat Kapitel erfolgreich aktualisiert:',
|
||||
'chapter_delete' => 'hat Kapitel gelöscht',
|
||||
'chapter_delete_notification' => 'hat Kapitel erfolgreich gelöscht:',
|
||||
'chapter_move' => 'hat Kapitel verschoben:',
|
||||
'chapter_create' => 'Kapitel erstellt',
|
||||
'chapter_create_notification' => 'Das Kapitel wurde erfolgreich erstellt.',
|
||||
'chapter_update' => 'Kapitel aktualisiert',
|
||||
'chapter_update_notification' => 'Das Kapitel wurde erfolgreich aktualisiert.',
|
||||
'chapter_delete' => 'Kapitel gelöscht',
|
||||
'chapter_delete_notification' => 'Das Kapitel wurde erfolgreich gelöscht.',
|
||||
'chapter_move' => 'Kapitel verschoben',
|
||||
|
||||
// Books
|
||||
'book_create' => 'hat Buch erstellt:',
|
||||
'book_create_notification' => 'hat Buch erfolgreich erstellt:',
|
||||
'book_update' => 'hat Buch aktualisiert:',
|
||||
'book_update_notification' => 'hat Buch erfolgreich aktualisiert:',
|
||||
'book_delete' => 'hat Buch gelöscht:',
|
||||
'book_delete_notification' => 'hat Buch erfolgreich gelöscht:',
|
||||
'book_sort' => 'hat Buch sortiert:',
|
||||
'book_sort_notification' => 'hat Buch erfolgreich neu sortiert:',
|
||||
'book_create' => 'Buch erstellt',
|
||||
'book_create_notification' => 'Das Buch wurde erfolgreich erstellt.',
|
||||
'book_update' => 'Buch aktualisiert',
|
||||
'book_update_notification' => 'Das Buch wurde erfolgreich aktualisiert.',
|
||||
'book_delete' => 'Buch gelöscht',
|
||||
'book_delete_notification' => 'Das Buch wurde erfolgreich gelöscht.',
|
||||
'book_sort' => 'Buch sortiert',
|
||||
'book_sort_notification' => 'Das Buch wurde erfolgreich neu sortiert.',
|
||||
|
||||
// Other
|
||||
'commented_on' => 'kommentierte',
|
||||
|
@ -179,6 +179,7 @@ return [
|
||||
'pages_revisions_restore' => 'Wiederherstellen',
|
||||
'pages_revisions_none' => 'Diese Seite hat keine älteren Versionen.',
|
||||
'pages_copy_link' => 'Link kopieren',
|
||||
'pages_edit_content_link' => 'Inhalt bearbeiten',
|
||||
'pages_permissions_active' => 'Seiten-Berechtigungen aktiv',
|
||||
'pages_initial_revision' => 'Erste Veröffentlichung',
|
||||
'pages_initial_name' => 'Neue Seite',
|
||||
|
@ -185,6 +185,7 @@ return [
|
||||
'pages_revisions_restore' => 'Restore',
|
||||
'pages_revisions_none' => 'This page has no revisions',
|
||||
'pages_copy_link' => 'Copy Link',
|
||||
'pages_edit_content_link' => 'Edit Content',
|
||||
'pages_permissions_active' => 'Page Permissions Active',
|
||||
'pages_initial_revision' => 'Initial publish',
|
||||
'pages_initial_name' => 'New Page',
|
||||
|
@ -82,6 +82,7 @@ return [
|
||||
'role_details' => 'Role Details',
|
||||
'role_name' => 'Role Name',
|
||||
'role_desc' => 'Short Description of Role',
|
||||
'role_external_auth_id' => 'External Authentication IDs',
|
||||
'role_system' => 'System Permissions',
|
||||
'role_manage_users' => 'Manage users',
|
||||
'role_manage_roles' => 'Manage roles & role permissions',
|
||||
|
@ -35,6 +35,8 @@ return [
|
||||
'book_delete' => 'libro borrado',
|
||||
'book_delete_notification' => 'Libro borrado exitosamente',
|
||||
'book_sort' => 'libro ordenado',
|
||||
'book_sort_notification' => 'Libro re-ordenado exitosamente',
|
||||
'book_sort_notification' => 'Libro reordenado exitosamente',
|
||||
|
||||
// Other
|
||||
'commented_on' => 'comentada el',
|
||||
];
|
||||
|
@ -31,6 +31,7 @@ return [
|
||||
'edit' => 'Editar',
|
||||
'sort' => 'Ordenar',
|
||||
'move' => 'Mover',
|
||||
'copy' => 'Copiar',
|
||||
'reply' => 'Responder',
|
||||
'delete' => 'Borrar',
|
||||
'search' => 'Buscar',
|
||||
|
@ -166,6 +166,9 @@ return [
|
||||
'pages_not_in_chapter' => 'La página no está en un capítulo',
|
||||
'pages_move' => 'Mover página',
|
||||
'pages_move_success' => 'Página movida a ":parentName"',
|
||||
'pages_copy' => 'Copiar página',
|
||||
'pages_copy_desination' => 'Destino de la copia',
|
||||
'pages_copy_success' => 'Página copiada a correctamente',
|
||||
'pages_permissions' => 'Permisos de página',
|
||||
'pages_permissions_success' => 'Permisos de página actualizados',
|
||||
'pages_revision' => 'Revisión',
|
||||
@ -182,6 +185,7 @@ return [
|
||||
'pages_revisions_restore' => 'Restaurar',
|
||||
'pages_revisions_none' => 'Esta página no tiene revisiones',
|
||||
'pages_copy_link' => 'Copiar Enlace',
|
||||
'pages_edit_content_link' => 'Contenido editado',
|
||||
'pages_permissions_active' => 'Permisos de página activos',
|
||||
'pages_initial_revision' => 'Publicación inicial',
|
||||
'pages_initial_name' => 'Página nueva',
|
||||
|
@ -7,7 +7,7 @@ return [
|
||||
*/
|
||||
|
||||
// Permissions
|
||||
'permission' => 'UNo tiene permisos para visualizar la página solicitada.',
|
||||
'permission' => 'No tiene permisos para visualizar la página solicitada.',
|
||||
'permissionJson' => 'No tiene permisos para ejecutar la acción solicitada.',
|
||||
|
||||
// Auth
|
||||
@ -65,7 +65,7 @@ return [
|
||||
'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 nuevos registros',
|
||||
|
||||
// Comments
|
||||
// Comments
|
||||
'comment_list' => 'Se ha producido un error al buscar los comentarios.',
|
||||
'cannot_add_comment_to_draft' => 'No puedes añadir comentarios a un borrador.',
|
||||
'comment_add' => 'Se ha producido un error al añadir el comentario.',
|
||||
|
@ -34,6 +34,7 @@ return [
|
||||
'app_homepage' => 'Página de inicio',
|
||||
'app_homepage_desc' => 'Elija la página que se mostrará al inicio en lugar de la vista predeterminada. Se ignorarán los permisos de la página seleccionada.',
|
||||
'app_homepage_default' => 'Página de inicio seleccionada',
|
||||
'app_homepage_books' => 'O selecciona la página de libros como página de inicio. Esto prevalecerá sobre cualquier página seleccionada como página de inicio.',
|
||||
'app_disable_comments' => 'Deshabilitar comentarios',
|
||||
'app_disable_comments_desc' => 'Deshabilita los comentarios en todas las páginas de la aplicación. Los comentarios existentes no se muestran.',
|
||||
|
||||
@ -50,6 +51,19 @@ return [
|
||||
'reg_confirm_restrict_domain_desc' => 'Introduzca una lista separada por comas de los dominio a los que les gustaría restringir el registro de usuarios. A los usuarios les será enviado un correo electrónico para confirmar la dirección antes de que se le permita interactuar con la aplicación. <br> Tenga en cuenta que los usuarios podrán cambiar sus direcciones de correo electrónico después de registrarse exitosamente.',
|
||||
'reg_confirm_restrict_domain_placeholder' => 'Ninguna restricción establecida',
|
||||
|
||||
/**
|
||||
* Maintenance settings
|
||||
*/
|
||||
|
||||
'maint' => 'Mantenimiento',
|
||||
'maint_image_cleanup' => 'Limpiar imágenes',
|
||||
'maint_image_cleanup_desc' => "Analiza las páginas y sus revisiones para comprobar qué imágenes y dibujos están siendo utilizadas y cuales no son necesarias. Asegúrate de crear una copia completa de la base de datos y de las imágenes antes de lanzar esta opción.",
|
||||
'maint_image_cleanup_ignore_revisions' => 'Ignorar imágenes en revisiones',
|
||||
'maint_image_cleanup_run' => 'Lanzar limpieza',
|
||||
'maint_image_cleanup_warning' => 'Se han encontrado :count imágenes posiblemente no utilizadas . ¿Estás seguro de querer borrar estas imágenes?',
|
||||
'maint_image_cleanup_success' => '¡Se han encontrado y borrado :count imágenes posiblemente no utilizadas!',
|
||||
'maint_image_cleanup_nothing_found' => '¡No se han encontrado imágenes sin utilizar, no se han borrado imágenes!',
|
||||
|
||||
/**
|
||||
* Role settings
|
||||
*/
|
||||
|
@ -124,7 +124,7 @@ return [
|
||||
'chapters_permissions_active' => 'Permisos de capítulo activado',
|
||||
'chapters_permissions_success' => 'Permisos de capítulo actualizados',
|
||||
'chapters_search_this' => 'Buscar en este capítulo',
|
||||
|
||||
|
||||
/**
|
||||
* Pages
|
||||
*/
|
||||
@ -185,6 +185,7 @@ return [
|
||||
'pages_revisions_restore' => 'Restaurar',
|
||||
'pages_revisions_none' => 'Esta página no tiene revisiones',
|
||||
'pages_copy_link' => 'Copiar enlace',
|
||||
'pages_edit_content_link' => 'Contenido editado',
|
||||
'pages_permissions_active' => 'Permisos de página activos',
|
||||
'pages_initial_revision' => 'Publicación inicial',
|
||||
'pages_initial_name' => 'Página nueva',
|
||||
|
@ -34,10 +34,10 @@ return [
|
||||
'app_homepage' => 'Página de inicio de la Aplicación',
|
||||
'app_homepage_desc' => 'Seleccione una página de inicio para mostrar en lugar de la vista por defecto. Se ignoran los permisos de página para las páginas seleccionadas.',
|
||||
'app_homepage_default' => 'Página de inicio por defecto seleccionadad',
|
||||
'app_homepage_books' => 'O seleccione la página de libros como su página de inicio. Esto tendrá preferencia sobre cualquier página seleccionada como página de inicio.',
|
||||
'app_disable_comments' => 'Deshabilitar comentarios',
|
||||
'app_disable_comments_desc' => 'Deshabilitar comentarios en todas las páginas de la aplicación. Los comentarios existentes no se muestran.',
|
||||
|
||||
|
||||
/**
|
||||
* Registration settings
|
||||
*/
|
||||
@ -51,6 +51,19 @@ return [
|
||||
'reg_confirm_restrict_domain_desc' => 'Introduzca una lista separada por comas de los correos electrónicos del dominio a los que les gustaría restringir el registro por dominio. A los usuarios les será enviado un correo elctrónico para confirmar la dirección antes de que se le permita interactuar con la aplicación. <br> Note que a los usuarios se les permitirá cambiar sus direcciones de correo electrónico luego de un registro éxioso.',
|
||||
'reg_confirm_restrict_domain_placeholder' => 'Ninguna restricción establecida',
|
||||
|
||||
/**
|
||||
* Maintenance settings
|
||||
*/
|
||||
|
||||
'maint' => 'Mantenimiento',
|
||||
'maint_image_cleanup' => 'Limpiar imágenes',
|
||||
'maint_image_cleanup_desc' => "Analizar contenido de páginas y revisiones para detectar cuáles imágenes y dibujos están en uso y cuáles son redundantes. Asegúrese de crear un respaldo completo de imágenes y base de datos antes de ejecutar esta tarea.",
|
||||
'maint_image_cleanup_ignore_revisions' => 'Ignorar imágenes en revisión',
|
||||
'maint_image_cleanup_run' => 'Ejecutar limpieza',
|
||||
'maint_image_cleanup_warning' => 'Se encontraron :count imágenes pontencialmente sin uso. Está seguro de que quiere eliminarlas?',
|
||||
'maint_image_cleanup_success' => 'Se encontraron y se eliminaron :count imágenes pontencialmente sin uso!',
|
||||
'maint_image_cleanup_nothing_found' => 'No se encotraron imágenes sin usar, Nada eliminado!',
|
||||
|
||||
/**
|
||||
* Role settings
|
||||
*/
|
||||
|
@ -15,7 +15,7 @@ return [
|
||||
'page_delete' => 'a supprimé la page',
|
||||
'page_delete_notification' => 'Page supprimée avec succès',
|
||||
'page_restore' => 'a restauré la page',
|
||||
'page_restore_notification' => 'Page réstaurée avec succès',
|
||||
'page_restore_notification' => 'Page restaurée avec succès',
|
||||
'page_move' => 'a déplacé la page',
|
||||
|
||||
// Chapters
|
||||
@ -39,5 +39,4 @@ return [
|
||||
|
||||
// Other
|
||||
'commented_on' => 'a commenté'
|
||||
|
||||
];
|
||||
|
@ -73,4 +73,4 @@ return [
|
||||
'email_not_confirmed_click_link' => 'Merci de cliquer sur le lien dans l\'e-mail qui vous a été envoyé après l\'enregistrement.',
|
||||
'email_not_confirmed_resend' => 'Si vous ne retrouvez plus l\'e-mail, vous pouvez renvoyer un e-mail de confirmation en utilisant le formulaire ci-dessous.',
|
||||
'email_not_confirmed_resend_button' => 'Renvoyez l\'e-mail de confirmation',
|
||||
];
|
||||
];
|
@ -20,6 +20,7 @@ return [
|
||||
'role' => 'Rôle',
|
||||
'cover_image' => 'Image de couverture',
|
||||
'cover_image_description' => 'Cette image doit être environ 300x170px.',
|
||||
|
||||
/**
|
||||
* Actions
|
||||
*/
|
||||
@ -30,6 +31,7 @@ return [
|
||||
'edit' => 'Editer',
|
||||
'sort' => 'Trier',
|
||||
'move' => 'Déplacer',
|
||||
'copy' => 'Copier',
|
||||
'reply' => 'Répondre',
|
||||
'delete' => 'Supprimer',
|
||||
'search' => 'Chercher',
|
||||
@ -38,7 +40,6 @@ return [
|
||||
'remove' => 'Enlever',
|
||||
'add' => 'Ajouter',
|
||||
|
||||
|
||||
/**
|
||||
* Misc
|
||||
*/
|
||||
@ -63,4 +64,4 @@ return [
|
||||
*/
|
||||
'email_action_help' => 'Si vous rencontrez des problèmes pour cliquer sur le bouton ":actionText", copiez et collez l\'adresse ci-dessous dans votre navigateur :',
|
||||
'email_rights' => 'Tous droits réservés',
|
||||
];
|
||||
];
|
@ -4,7 +4,7 @@ return [
|
||||
/**
|
||||
* Image Manager
|
||||
*/
|
||||
'image_select' => 'Selectionner une image',
|
||||
'image_select' => 'Sélectionner une image',
|
||||
'image_all' => 'Toutes',
|
||||
'image_all_title' => 'Voir toutes les images',
|
||||
'image_book_title' => 'Voir les images ajoutées à ce livre',
|
||||
@ -15,19 +15,20 @@ return [
|
||||
'image_image_name' => 'Nom de l\'image',
|
||||
'image_delete_used' => 'Cette image est utilisée dans les pages ci-dessous.',
|
||||
'image_delete_confirm' => 'Confirmez que vous souhaitez bien supprimer cette image.',
|
||||
'image_select_image' => 'Selectionner l\'image',
|
||||
'image_select_image' => 'Sélectionner l\'image',
|
||||
'image_dropzone' => 'Glissez les images ici ou cliquez pour les ajouter',
|
||||
'images_deleted' => 'Images supprimées',
|
||||
'image_preview' => 'Prévisualiser l\'image',
|
||||
'image_upload_success' => 'Image ajoutée avec succès',
|
||||
'image_update_success' => 'Détails de l\'image mis à jour',
|
||||
'image_delete_success' => 'Image supprimée avec succès',
|
||||
'image_upload_remove' => 'Supprimer',
|
||||
|
||||
/**
|
||||
* Code editor
|
||||
*/
|
||||
'code_editor' => 'Editer le code',
|
||||
'code_language' => 'Language du code',
|
||||
'code_language' => 'Langage du code',
|
||||
'code_content' => 'Contenu du code',
|
||||
'code_save' => 'Enregistrer le code',
|
||||
];
|
||||
|
@ -19,7 +19,6 @@ return [
|
||||
'meta_created_name' => 'Créé :timeLength par :user',
|
||||
'meta_updated' => 'Mis à jour :timeLength',
|
||||
'meta_updated_name' => 'Mis à jour :timeLength par :user',
|
||||
'x_pages' => ':count pages',
|
||||
'entity_select' => 'Sélectionner l\'entité',
|
||||
'images' => 'Images',
|
||||
'my_recent_drafts' => 'Mes brouillons récents',
|
||||
@ -36,7 +35,7 @@ return [
|
||||
* Permissions and restrictions
|
||||
*/
|
||||
'permissions' => 'Permissions',
|
||||
'permissions_intro' => 'Une fois activées ces permission prendont la priorité sur tous les sets de permissions pré-existants.',
|
||||
'permissions_intro' => 'Une fois activées ces permissions prendront la priorité sur tous les sets de permissions préexistants.',
|
||||
'permissions_enable' => 'Activer les permissions personnalisées',
|
||||
'permissions_save' => 'Enregistrer les permissions',
|
||||
|
||||
@ -131,6 +130,7 @@ return [
|
||||
*/
|
||||
'page' => 'Page',
|
||||
'pages' => 'Pages',
|
||||
'x_pages' => ':count Page|:count Pages',
|
||||
'pages_popular' => 'Pages populaires',
|
||||
'pages_new' => 'Nouvelle page',
|
||||
'pages_attachments' => 'Fichiers joints',
|
||||
@ -166,6 +166,9 @@ return [
|
||||
'pages_not_in_chapter' => 'La page n\'est pas dans un chapitre',
|
||||
'pages_move' => 'Déplacer la page',
|
||||
'pages_move_success' => 'Page déplacée à ":parentName"',
|
||||
'pages_copy' => 'Copier la page',
|
||||
'pages_copy_desination' => 'Destination de la copie',
|
||||
'pages_copy_success' => 'Page copiée avec succès',
|
||||
'pages_permissions' => 'Permissions de la page',
|
||||
'pages_permissions_success' => 'Permissions de la page mises à jour',
|
||||
'pages_revision' => 'Révision',
|
||||
@ -182,6 +185,7 @@ return [
|
||||
'pages_revisions_restore' => 'Restaurer',
|
||||
'pages_revisions_none' => 'Cette page n\'a aucune révision',
|
||||
'pages_copy_link' => 'Copier le lien',
|
||||
'pages_edit_content_link' => 'Modifier le contenu',
|
||||
'pages_permissions_active' => 'Permissions de page actives',
|
||||
'pages_initial_revision' => 'Publication initiale',
|
||||
'pages_initial_name' => 'Nouvelle page',
|
||||
@ -200,10 +204,12 @@ return [
|
||||
* Editor sidebar
|
||||
*/
|
||||
'page_tags' => 'Mots-clés de la page',
|
||||
'chapter_tags' => 'Mots-clés du chapitre',
|
||||
'book_tags' => 'Mots-clés du livre',
|
||||
'tag' => 'Mot-clé',
|
||||
'tags' => 'Mots-clé',
|
||||
'tags' => 'Mots-clés',
|
||||
'tag_value' => 'Valeur du mot-clé (Optionnel)',
|
||||
'tags_explain' => "Ajouter des mot-clés pour catégoriser votre contenu.",
|
||||
'tags_explain' => "Ajouter des mots-clés pour catégoriser votre contenu.",
|
||||
'tags_add' => 'Ajouter un autre mot-clé',
|
||||
'attachments' => 'Fichiers joints',
|
||||
'attachments_explain' => 'Ajouter des fichiers ou des liens pour les afficher sur votre page. Ils seront affichés dans la barre latérale',
|
||||
@ -257,6 +263,6 @@ return [
|
||||
'comment_deleted_success' => 'Commentaire supprimé',
|
||||
'comment_created_success' => 'Commentaire ajouté',
|
||||
'comment_updated_success' => 'Commentaire mis à jour',
|
||||
'comment_delete_confirm' => 'Etes-vous sûr de vouloir supprimer ce commentaire?',
|
||||
'comment_delete_confirm' => 'Etes-vous sûr de vouloir supprimer ce commentaire ?',
|
||||
'comment_in_reply_to' => 'En réponse à :commentId',
|
||||
];
|
||||
];
|
@ -14,12 +14,13 @@ return [
|
||||
'error_user_exists_different_creds' => 'Un utilisateur avec l\'adresse :email existe déjà.',
|
||||
'email_already_confirmed' => 'Cet e-mail a déjà été validé, vous pouvez vous connecter.',
|
||||
'email_confirmation_invalid' => 'Cette confirmation est invalide. Veuillez essayer de vous inscrire à nouveau.',
|
||||
'email_confirmation_expired' => 'Le jeton de confirmation est perimé. Un nouvel e-mail vous a été envoyé.',
|
||||
'email_confirmation_expired' => 'Le jeton de confirmation est périmé. Un nouvel e-mail vous a été envoyé.',
|
||||
'ldap_fail_anonymous' => 'L\'accès LDAP anonyme n\'a pas abouti',
|
||||
'ldap_fail_authed' => 'L\'accès LDAP n\'a pas abouti avec cet utilisateur et ce mot de passe',
|
||||
'ldap_extension_not_installed' => 'L\'extention LDAP PHP n\'est pas installée',
|
||||
'ldap_extension_not_installed' => 'L\'extension LDAP PHP n\'est pas installée',
|
||||
'ldap_cannot_connect' => 'Impossible de se connecter au serveur LDAP, la connexion initiale a échoué',
|
||||
'social_no_action_defined' => 'Pas d\'action définie',
|
||||
'social_login_bad_response' => "Erreur pendant la tentative de connexion à :socialAccount : \n:error",
|
||||
'social_account_in_use' => 'Ce compte :socialAccount est déjà utilisé. Essayez de vous connecter via :socialAccount.',
|
||||
'social_account_email_in_use' => 'L\'email :email est déjà utilisé. Si vous avez déjà un compte :socialAccount, vous pouvez le joindre à votre profil existant.',
|
||||
'social_account_existing' => 'Ce compte :socialAccount est déjà rattaché à votre profil.',
|
||||
@ -34,13 +35,17 @@ return [
|
||||
'cannot_get_image_from_url' => 'Impossible de récupérer l\'image depuis :url',
|
||||
'cannot_create_thumbs' => 'Le serveur ne peut pas créer de miniature, vérifier que l\'extension PHP GD est installée.',
|
||||
'server_upload_limit' => 'La taille du fichier est trop grande.',
|
||||
'uploaded' => 'Le serveur n\'autorise pas l\'envoi d\'un fichier de cette taille. Veuillez essayer avec une taille de fichier réduite.',
|
||||
'image_upload_error' => 'Une erreur est survenue pendant l\'envoi de l\'image',
|
||||
'image_upload_type_error' => 'LE format de l\'image envoyée n\'est pas valide',
|
||||
|
||||
// Attachments
|
||||
'attachment_page_mismatch' => 'Page mismatch during attachment update',
|
||||
'attachment_page_mismatch' => 'Page incorrecte durant la mise à jour du fichier joint',
|
||||
'attachment_not_found' => 'Fichier joint non trouvé',
|
||||
|
||||
// Pages
|
||||
'page_draft_autosave_fail' => 'Le brouillon n\'a pas pu être sauvé. Vérifiez votre connexion internet',
|
||||
'page_custom_home_deletion' => 'Impossible de supprimer une page définie comme page d\'accueil',
|
||||
|
||||
// Entities
|
||||
'entity_not_found' => 'Entité non trouvée',
|
||||
|
@ -34,8 +34,10 @@ return [
|
||||
'app_homepage' => 'Page d\'accueil de l\'application',
|
||||
'app_homepage_desc' => 'Choisissez une page à afficher sur la page d\'accueil au lieu de la vue par défaut. Les permissions sont ignorées pour les pages sélectionnées.',
|
||||
'app_homepage_default' => 'Page d\'accueil par défaut sélectionnée',
|
||||
'app_homepage_books' => 'Ou sélectionner la page des livres comme page d\'accueil. Cela va ignorer la page séléctionnée comme page d\'accueil.',
|
||||
'app_disable_comments' => 'Désactiver les commentaires',
|
||||
'app_disable_comments_desc' => 'Désactive les commentaires sur toutes les pages de l\'application. Les commentaires existants ne sont pas affichés.',
|
||||
|
||||
/**
|
||||
* Registration settings
|
||||
*/
|
||||
@ -46,9 +48,22 @@ return [
|
||||
'reg_confirm_email' => 'Obliger la confirmation par e-mail ?',
|
||||
'reg_confirm_email_desc' => 'Si la restriction de domaine est activée, la confirmation sera automatiquement obligatoire et cette valeur sera ignorée.',
|
||||
'reg_confirm_restrict_domain' => 'Restreindre l\'inscription à un domaine',
|
||||
'reg_confirm_restrict_domain_desc' => 'Entrez une liste de domaines acceptés lors de l\'inscription, séparés par une virgule. Les utilisateur recevront un e-mail de confirmation à cette adresse. <br> Les utilisateurs pourront changer leur adresse après inscription s\'ils le souhaitent.',
|
||||
'reg_confirm_restrict_domain_desc' => 'Entrez une liste de domaines acceptés lors de l\'inscription, séparés par une virgule. Les utilisateurs recevront un e-mail de confirmation à cette adresse. <br> Les utilisateurs pourront changer leur adresse après inscription s\'ils le souhaitent.',
|
||||
'reg_confirm_restrict_domain_placeholder' => 'Aucune restriction en place',
|
||||
|
||||
/**
|
||||
* Maintenance settings
|
||||
*/
|
||||
|
||||
'maint' => 'Maintenance',
|
||||
'maint_image_cleanup' => 'Nettoyer les images',
|
||||
'maint_image_cleanup_desc' => "Scan le contenu des pages et des révisions pour vérifier les images et les dessins en cours d'utilisation et lesquels sont redondant. Veuillez à faire une sauvegarde de la base de données et des images avant de lancer ceci.",
|
||||
'maint_image_cleanup_ignore_revisions' => 'Ignorer les images dans les révisions',
|
||||
'maint_image_cleanup_run' => 'Lancer le nettoyage',
|
||||
'maint_image_cleanup_warning' => ':count images potentiellement inutilisées trouvées. Etes-vous sûr de vouloir supprimer ces images ?',
|
||||
'maint_image_cleanup_success' => ':count images potentiellement inutilisées trouvées et supprimées !',
|
||||
'maint_image_cleanup_nothing_found' => 'Aucune image inutilisée trouvée, rien à supprimer !',
|
||||
|
||||
/**
|
||||
* Role settings
|
||||
*/
|
||||
@ -61,23 +76,24 @@ return [
|
||||
'role_delete_confirm' => 'Ceci va supprimer le rôle \':roleName\'.',
|
||||
'role_delete_users_assigned' => 'Ce rôle a :userCount utilisateurs assignés. Vous pouvez choisir un rôle de remplacement pour ces utilisateurs.',
|
||||
'role_delete_no_migration' => "Ne pas assigner de nouveau rôle",
|
||||
'role_delete_sure' => 'Êtes vous sûr(e) de vouloir supprimer ce rôle ?',
|
||||
'role_delete_sure' => 'Êtes-vous sûr de vouloir supprimer ce rôle ?',
|
||||
'role_delete_success' => 'Le rôle a été supprimé avec succès',
|
||||
'role_edit' => 'Modifier le rôle',
|
||||
'role_details' => 'Détails du rôle',
|
||||
'role_name' => 'Nom du rôle',
|
||||
'role_desc' => 'Courte description du rôle',
|
||||
'role_external_auth_id' => 'Identifiants d\'authentification externes',
|
||||
'role_system' => 'Permissions système',
|
||||
'role_manage_users' => 'Gérer les utilisateurs',
|
||||
'role_manage_roles' => 'Gérer les rôles et permissions',
|
||||
'role_manage_entity_permissions' => 'Gérer les permissions sur les livres, chapitres et pages',
|
||||
'role_manage_own_entity_permissions' => 'Gérer les permissions de ses propres livres, chapitres, et pages',
|
||||
'role_manage_settings' => 'Gérer les préférences de l\'application',
|
||||
'role_asset' => 'Asset Permissions',
|
||||
'role_asset_desc' => 'These permissions control default access to the assets within the system. Permissions on Books, Chapters and Pages will override these permissions.',
|
||||
'role_asset' => 'Permissions des ressources',
|
||||
'role_asset_desc' => 'Ces permissions contrôlent l\'accès par défaut des ressources dans le système. Les permissions dans les livres, les chapitres et les pages ignoreront ces permissions',
|
||||
'role_all' => 'Tous',
|
||||
'role_own' => 'Propres',
|
||||
'role_controlled_by_asset' => 'Controlled by the asset they are uploaded to',
|
||||
'role_controlled_by_asset' => 'Contrôlé par les ressources les ayant envoyés',
|
||||
'role_save' => 'Enregistrer le rôle',
|
||||
'role_update_success' => 'Rôle mis à jour avec succès',
|
||||
'role_users' => 'Utilisateurs ayant ce rôle',
|
||||
@ -93,7 +109,7 @@ return [
|
||||
'users_search' => 'Chercher les utilisateurs',
|
||||
'users_role' => 'Rôles des utilisateurs',
|
||||
'users_external_auth_id' => 'Identifiant d\'authentification externe',
|
||||
'users_password_warning' => 'Remplissez ce fomulaire uniquement si vous souhaitez changer de mot de passe:',
|
||||
'users_password_warning' => 'Remplissez ce formulaire uniquement si vous souhaitez changer de mot de passe:',
|
||||
'users_system_public' => 'Cet utilisateur représente les invités visitant votre instance. Il est assigné automatiquement aux invités.',
|
||||
'users_books_view_type' => 'Disposition d\'affichage préférée pour les livres',
|
||||
'users_delete' => 'Supprimer un utilisateur',
|
||||
|
@ -182,6 +182,7 @@ return [
|
||||
'pages_revisions_restore' => 'Ripristina',
|
||||
'pages_revisions_none' => 'Questa pagina non ha versioni',
|
||||
'pages_copy_link' => 'Copia Link',
|
||||
'pages_edit_content_link' => 'Modifica contenuto',
|
||||
'pages_permissions_active' => 'Permessi Pagina Attivi',
|
||||
'pages_initial_revision' => 'Pubblicazione iniziale',
|
||||
'pages_initial_name' => 'Nuova Pagina',
|
||||
|
@ -179,6 +179,7 @@ return [
|
||||
'pages_revisions_restore' => '復元',
|
||||
'pages_revisions_none' => 'このページにはリビジョンがありません',
|
||||
'pages_copy_link' => 'リンクをコピー',
|
||||
'pages_edit_content_link' => 'コンテンツの編集',
|
||||
'pages_permissions_active' => 'ページの権限は有効です',
|
||||
'pages_initial_revision' => '初回の公開',
|
||||
'pages_initial_name' => '新規ページ',
|
||||
|
@ -14,7 +14,7 @@ return [
|
||||
'recent_activity' => 'Recente Activiteit',
|
||||
'create_now' => 'Maak er zelf één',
|
||||
'revisions' => 'Revisies',
|
||||
'meta_revision' => 'Revisie #:revisionCount',
|
||||
'meta_revision' => 'Revisie #:revisionCount',
|
||||
'meta_created' => 'Aangemaakt :timeLength',
|
||||
'meta_created_name' => 'Aangemaakt: :timeLength door :user',
|
||||
'meta_updated' => ':timeLength Aangepast',
|
||||
@ -44,7 +44,7 @@ return [
|
||||
* Search
|
||||
*/
|
||||
'search_results' => 'Zoekresultaten',
|
||||
'search_total_results_found' => ':count resultaten gevonden|:count resultaten gevonden',
|
||||
'search_total_results_found' => ':count resultaten gevonden|:count resultaten gevonden',
|
||||
'search_clear' => 'Zoekopdracht wissen',
|
||||
'search_no_pages' => 'Er zijn geen pagina\'s gevonden',
|
||||
'search_for_term' => 'Zoeken op :term',
|
||||
@ -105,7 +105,7 @@ return [
|
||||
*/
|
||||
'chapter' => 'Hoofdstuk',
|
||||
'chapters' => 'Hoofdstukken',
|
||||
'x_chapters' => ':count Hoofdstuk|:count Hoofdstukken',
|
||||
'x_chapters' => ':count Hoofdstuk|:count Hoofdstukken',
|
||||
'chapters_popular' => 'Populaire Hoofdstukken',
|
||||
'chapters_new' => 'Nieuw Hoofdstuk',
|
||||
'chapters_create' => 'Hoofdstuk Toevoegen',
|
||||
@ -124,14 +124,14 @@ return [
|
||||
'chapters_empty' => 'Er zijn geen pagina\'s in dit hoofdstuk aangemaakt.',
|
||||
'chapters_permissions_active' => 'Hoofdstuk Permissies Actief',
|
||||
'chapters_permissions_success' => 'Hoofdstuk Permissies Bijgewerkt',
|
||||
'chapters_search_this' => 'Doorzoek dit hoofdstuk',
|
||||
'chapters_search_this' => 'Doorzoek dit hoofdstuk',
|
||||
|
||||
/**
|
||||
* Pages
|
||||
*/
|
||||
'page' => 'Pagina',
|
||||
'pages' => 'Pagina\'s',
|
||||
'x_pages' => ':count Pagina|:count Pagina\'s',
|
||||
'x_pages' => ':count Pagina|:count Pagina\'s',
|
||||
'pages_popular' => 'Populaire Pagina\'s',
|
||||
'pages_new' => 'Nieuwe Pagina',
|
||||
'pages_attachments' => 'Bijlages',
|
||||
@ -168,7 +168,7 @@ return [
|
||||
'pages_move_success' => 'Pagina verplaatst naar ":parentName"',
|
||||
'pages_permissions' => 'Pagina Permissies',
|
||||
'pages_permissions_success' => 'Pagina Permissies bijgwerkt',
|
||||
'pages_revision' => 'Revisie',
|
||||
'pages_revision' => 'Revisie',
|
||||
'pages_revisions' => 'Pagina Revisies',
|
||||
'pages_revisions_named' => 'Pagina Revisies voor :pageName',
|
||||
'pages_revision_named' => 'Pagina Revisie voor :pageName',
|
||||
@ -182,6 +182,7 @@ return [
|
||||
'pages_revisions_restore' => 'Herstellen',
|
||||
'pages_revisions_none' => 'Deze pagina heeft geen revisies',
|
||||
'pages_copy_link' => 'Link Kopiëren',
|
||||
'pages_edit_content_link' => 'Bewerk inhoud',
|
||||
'pages_permissions_active' => 'Pagina Permissies Actief',
|
||||
'pages_initial_revision' => 'Eerste publicatie',
|
||||
'pages_initial_name' => 'Nieuwe Pagina',
|
||||
|
@ -179,6 +179,7 @@ return [
|
||||
'pages_revisions_restore' => 'Przywróć',
|
||||
'pages_revisions_none' => 'Ta strona nie posiada żadnych rewizji',
|
||||
'pages_copy_link' => 'Kopiuj link',
|
||||
'pages_edit_content_link' => 'Edytuj zawartość',
|
||||
'pages_permissions_active' => 'Uprawnienia strony aktywne',
|
||||
'pages_initial_revision' => 'Wydanie pierwotne',
|
||||
'pages_initial_name' => 'Nowa strona',
|
||||
|
@ -20,6 +20,7 @@ return [
|
||||
'role' => 'Regra',
|
||||
'cover_image' => 'Imagem de capa',
|
||||
'cover_image_description' => 'Esta imagem deve ser aproximadamente 300x170px.',
|
||||
|
||||
/**
|
||||
* Actions
|
||||
*/
|
||||
@ -30,6 +31,7 @@ return [
|
||||
'edit' => 'Editar',
|
||||
'sort' => 'Ordenar',
|
||||
'move' => 'Mover',
|
||||
'copy' => 'Copiar',
|
||||
'reply' => 'Responder',
|
||||
'delete' => 'Excluir',
|
||||
'search' => 'Pesquisar',
|
||||
@ -48,6 +50,8 @@ return [
|
||||
'toggle_details' => 'Alternar Detalhes',
|
||||
'toggle_thumbnails' => 'Alternar Miniaturas',
|
||||
'details' => 'Detalhes',
|
||||
'grid_view' => 'Visualização em Grade',
|
||||
'list_view' => 'Visualização em Lista',
|
||||
|
||||
/**
|
||||
* Header
|
||||
|
@ -181,6 +181,7 @@ return [
|
||||
'pages_revisions_restore' => 'Restaurar',
|
||||
'pages_revisions_none' => 'Essa página não tem revisões',
|
||||
'pages_copy_link' => 'Copia Link',
|
||||
'pages_edit_content_link' => 'Editar conteúdo',
|
||||
'pages_permissions_active' => 'Permissões de Página Ativas',
|
||||
'pages_initial_revision' => 'Publicação Inicial',
|
||||
'pages_initial_name' => 'Nova Página',
|
||||
|
@ -181,6 +181,7 @@ return [
|
||||
'pages_revisions_restore' => 'Восстановить',
|
||||
'pages_revisions_none' => 'У этой страницы нет других версий',
|
||||
'pages_copy_link' => 'Копировать ссылку',
|
||||
'pages_edit_content_link' => 'Изменить содержание',
|
||||
'pages_permissions_active' => 'Действующие разрешения на страницу',
|
||||
'pages_initial_revision' => 'Первоначальное издание',
|
||||
'pages_initial_name' => 'Новая страница',
|
||||
|
@ -166,6 +166,7 @@ return [
|
||||
'pages_revisions_restore' => 'Obnoviť',
|
||||
'pages_revisions_none' => 'Táto stránka nemá žiadne revízie',
|
||||
'pages_copy_link' => 'Kopírovať odkaz',
|
||||
'pages_edit_content_link' => 'Upraviť obsah',
|
||||
'pages_permissions_active' => 'Oprávnienia stránky aktívne',
|
||||
'pages_initial_revision' => 'Prvé zverejnenie',
|
||||
'pages_initial_name' => 'Nová stránka',
|
||||
|
@ -182,6 +182,7 @@ return [
|
||||
'pages_revisions_restore' => 'Återställ',
|
||||
'pages_revisions_none' => 'Sidan har inga revisioner',
|
||||
'pages_copy_link' => 'Kopiera länk',
|
||||
'pages_edit_content_link' => 'Redigera innehåll',
|
||||
'pages_permissions_active' => 'Anpassade rättigheter är i bruk',
|
||||
'pages_initial_revision' => 'Första publicering',
|
||||
'pages_initial_name' => 'Ny sida',
|
||||
|
@ -181,6 +181,7 @@ return [
|
||||
'pages_revisions_restore' => '恢复',
|
||||
'pages_revisions_none' => '此页面没有修订',
|
||||
'pages_copy_link' => '复制链接',
|
||||
'pages_edit_content_link' => '编辑内容',
|
||||
'pages_permissions_active' => '有效的页面权限',
|
||||
'pages_initial_revision' => '初始发布',
|
||||
'pages_initial_name' => '新页面',
|
||||
|
@ -182,6 +182,7 @@ return [
|
||||
'pages_revisions_restore' => '恢複',
|
||||
'pages_revisions_none' => '此頁面沒有修訂',
|
||||
'pages_copy_link' => '複製連結',
|
||||
'pages_edit_content_link' => '编辑内容',
|
||||
'pages_permissions_active' => '有效的頁面權限',
|
||||
'pages_initial_revision' => '初次發布',
|
||||
'pages_initial_name' => '新頁面',
|
||||
|
@ -42,8 +42,8 @@
|
||||
<span class="text-primary small" @click="file.deleting = false;">{{ trans('common.cancel') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div @click="startEdit(file)" class="drag-card-action text-center text-primary" style="padding: 0;">@icon('edit')</div>
|
||||
<div @click="deleteFile(file)" class="drag-card-action text-center text-neg" style="padding: 0;">@icon('close')</div>
|
||||
<div @click="startEdit(file)" class="drag-card-action text-center text-primary">@icon('edit')</div>
|
||||
<div @click="deleteFile(file)" class="drag-card-action text-center text-neg">@icon('close')</div>
|
||||
</div>
|
||||
</draggable>
|
||||
<p class="small muted" v-if="files.length === 0">
|
||||
|
@ -14,7 +14,7 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-9">
|
||||
<div class="page-content">
|
||||
<div class="page-content page-revision">
|
||||
@include('pages.page-display')
|
||||
</div>
|
||||
</div>
|
||||
|
@ -124,10 +124,16 @@
|
||||
<div class="page-content flex" page-display="{{ $page->id }}">
|
||||
|
||||
<div class="pointer-container" id="pointer">
|
||||
<div class="pointer anim" >
|
||||
<div class="pointer anim {{ userCan('page-update', $page) ? 'is-page-editable' : ''}}" >
|
||||
<span class="icon text-primary">@icon('link') @icon('include', ['style' => 'display:none;'])</span>
|
||||
<input readonly="readonly" type="text" id="pointer-url" placeholder="url">
|
||||
<button class="button icon" data-clipboard-target="#pointer-url" type="button" title="{{ trans('entities.pages_copy_link') }}">@icon('copy')</button>
|
||||
<span class="input-group">
|
||||
<input readonly="readonly" type="text" id="pointer-url" placeholder="url">
|
||||
<button class="button icon" data-clipboard-target="#pointer-url" type="button" title="{{ trans('entities.pages_copy_link') }}">@icon('copy')</button>
|
||||
</span>
|
||||
@if(userCan('page-update', $page))
|
||||
<a href="{{ $page->getUrl('/edit') }}" id="pointer-edit" data-edit-href="{{ $page->getUrl('/edit') }}"
|
||||
class="button icon heading-edit-icon" title="{{ trans('entities.pages_edit_content_link')}}">@icon('edit')</a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1,12 +1,11 @@
|
||||
|
||||
<div notification="success" data-autohide class="pos" @if(session()->has('success')) data-show @endif>
|
||||
<div notification="success" style="display: none;" data-autohide class="pos" @if(session()->has('success')) data-show @endif>
|
||||
@icon('check-circle') <span>{!! nl2br(htmlentities(session()->get('success'))) !!}</span>
|
||||
</div>
|
||||
|
||||
<div notification="warning" class="warning" @if(session()->has('warning')) data-show @endif>
|
||||
<div notification="warning" style="display: none;" class="warning" @if(session()->has('warning')) data-show @endif>
|
||||
@icon('info') <span>{!! nl2br(htmlentities(session()->get('warning'))) !!}</span>
|
||||
</div>
|
||||
|
||||
<div notification="error" class="neg" @if(session()->has('error')) data-show @endif>
|
||||
<div notification="error" style="display: none;" class="neg" @if(session()->has('error')) data-show @endif>
|
||||
@icon('danger') <span>{!! nl2br(htmlentities(session()->get('error'))) !!}</span>
|
||||
</div>
|
||||
|
@ -15,6 +15,14 @@
|
||||
<label for="name">{{ trans('settings.role_desc') }}</label>
|
||||
@include('form/text', ['name' => 'description'])
|
||||
</div>
|
||||
|
||||
@if(config('auth.method') === 'ldap')
|
||||
<div class="form-group">
|
||||
<label for="name">{{ trans('settings.role_external_auth_id') }}</label>
|
||||
@include('form/text', ['name' => 'external_auth_id'])
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<h5>{{ trans('settings.role_system') }}</h5>
|
||||
<label>@include('settings/roles/checkbox', ['permission' => 'users-manage']) {{ trans('settings.role_manage_users') }}</label>
|
||||
<label>@include('settings/roles/checkbox', ['permission' => 'user-roles-manage']) {{ trans('settings.role_manage_roles') }}</label>
|
||||
|
36
resources/views/vendor/pagination/default.blade.php
vendored
Normal file
36
resources/views/vendor/pagination/default.blade.php
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
@if ($paginator->hasPages())
|
||||
<ul class="pagination">
|
||||
{{-- Previous Page Link --}}
|
||||
@if ($paginator->onFirstPage())
|
||||
<li class="disabled"><span>«</span></li>
|
||||
@else
|
||||
<li><a href="{{ $paginator->previousPageUrl() }}" rel="prev">«</a></li>
|
||||
@endif
|
||||
|
||||
{{-- Pagination Elements --}}
|
||||
@foreach ($elements as $element)
|
||||
{{-- "Three Dots" Separator --}}
|
||||
@if (is_string($element))
|
||||
<li class="disabled"><span>{{ $element }}</span></li>
|
||||
@endif
|
||||
|
||||
{{-- Array Of Links --}}
|
||||
@if (is_array($element))
|
||||
@foreach ($element as $page => $url)
|
||||
@if ($page == $paginator->currentPage())
|
||||
<li class="active primary-background"><span>{{ $page }}</span></li>
|
||||
@else
|
||||
<li><a href="{{ $url }}">{{ $page }}</a></li>
|
||||
@endif
|
||||
@endforeach
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
{{-- Next Page Link --}}
|
||||
@if ($paginator->hasMorePages())
|
||||
<li><a href="{{ $paginator->nextPageUrl() }}" rel="next">»</a></li>
|
||||
@else
|
||||
<li class="disabled"><span>»</span></li>
|
||||
@endif
|
||||
</ul>
|
||||
@endif
|
@ -1,10 +1,17 @@
|
||||
<?php namespace Tests;
|
||||
use BookStack\Role;
|
||||
use BookStack\Services\Ldap;
|
||||
use BookStack\User;
|
||||
use Mockery\MockInterface;
|
||||
|
||||
class LdapTest extends BrowserKitTest
|
||||
{
|
||||
|
||||
/**
|
||||
* @var MockInterface
|
||||
*/
|
||||
protected $mockLdap;
|
||||
|
||||
protected $mockUser;
|
||||
protected $resourceId = 'resource-test';
|
||||
|
||||
@ -12,9 +19,15 @@ class LdapTest extends BrowserKitTest
|
||||
{
|
||||
parent::setUp();
|
||||
if (!defined('LDAP_OPT_REFERRALS')) define('LDAP_OPT_REFERRALS', 1);
|
||||
app('config')->set(['auth.method' => 'ldap', 'services.ldap.base_dn' => 'dc=ldap,dc=local', 'auth.providers.users.driver' => 'ldap']);
|
||||
$this->mockLdap = \Mockery::mock(\BookStack\Services\Ldap::class);
|
||||
$this->app['BookStack\Services\Ldap'] = $this->mockLdap;
|
||||
app('config')->set([
|
||||
'auth.method' => 'ldap',
|
||||
'services.ldap.base_dn' => 'dc=ldap,dc=local',
|
||||
'services.ldap.email_attribute' => 'mail',
|
||||
'services.ldap.user_to_groups' => false,
|
||||
'auth.providers.users.driver' => 'ldap',
|
||||
]);
|
||||
$this->mockLdap = \Mockery::mock(Ldap::class);
|
||||
$this->app[Ldap::class] = $this->mockLdap;
|
||||
$this->mockUser = factory(User::class)->make();
|
||||
}
|
||||
|
||||
@ -133,4 +146,156 @@ class LdapTest extends BrowserKitTest
|
||||
->dontSee('External Authentication');
|
||||
}
|
||||
|
||||
public function test_login_maps_roles_and_retains_existsing_roles()
|
||||
{
|
||||
$roleToReceive = factory(Role::class)->create(['name' => 'ldaptester', 'display_name' => 'LdapTester']);
|
||||
$roleToReceive2 = factory(Role::class)->create(['name' => 'ldaptester-second', 'display_name' => 'LdapTester Second']);
|
||||
$existingRole = factory(Role::class)->create(['name' => 'ldaptester-existing']);
|
||||
$this->mockUser->forceFill(['external_auth_id' => $this->mockUser->name])->save();
|
||||
$this->mockUser->attachRole($existingRole);
|
||||
|
||||
app('config')->set([
|
||||
'services.ldap.user_to_groups' => true,
|
||||
'services.ldap.group_attribute' => 'memberOf',
|
||||
'services.ldap.remove_from_groups' => false,
|
||||
]);
|
||||
$this->mockLdap->shouldReceive('connect')->times(2)->andReturn($this->resourceId);
|
||||
$this->mockLdap->shouldReceive('setVersion')->times(2);
|
||||
$this->mockLdap->shouldReceive('setOption')->times(5);
|
||||
$this->mockLdap->shouldReceive('searchAndGetEntries')->times(5)
|
||||
->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')],
|
||||
'mail' => [$this->mockUser->email],
|
||||
'memberof' => [
|
||||
'count' => 2,
|
||||
0 => "cn=ldaptester,ou=groups,dc=example,dc=com",
|
||||
1 => "cn=ldaptester-second,ou=groups,dc=example,dc=com",
|
||||
]
|
||||
]]);
|
||||
$this->mockLdap->shouldReceive('bind')->times(6)->andReturn(true);
|
||||
|
||||
$this->visit('/login')
|
||||
->see('Username')
|
||||
->type($this->mockUser->name, '#username')
|
||||
->type($this->mockUser->password, '#password')
|
||||
->press('Log In')
|
||||
->seePageIs('/');
|
||||
|
||||
$user = User::where('email', $this->mockUser->email)->first();
|
||||
$this->seeInDatabase('role_user', [
|
||||
'user_id' => $user->id,
|
||||
'role_id' => $roleToReceive->id
|
||||
]);
|
||||
$this->seeInDatabase('role_user', [
|
||||
'user_id' => $user->id,
|
||||
'role_id' => $roleToReceive2->id
|
||||
]);
|
||||
$this->seeInDatabase('role_user', [
|
||||
'user_id' => $user->id,
|
||||
'role_id' => $existingRole->id
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_login_maps_roles_and_removes_old_roles_if_set()
|
||||
{
|
||||
$roleToReceive = factory(Role::class)->create(['name' => 'ldaptester', 'display_name' => 'LdapTester']);
|
||||
$existingRole = factory(Role::class)->create(['name' => 'ldaptester-existing']);
|
||||
$this->mockUser->forceFill(['external_auth_id' => $this->mockUser->name])->save();
|
||||
$this->mockUser->attachRole($existingRole);
|
||||
|
||||
app('config')->set([
|
||||
'services.ldap.user_to_groups' => true,
|
||||
'services.ldap.group_attribute' => 'memberOf',
|
||||
'services.ldap.remove_from_groups' => true,
|
||||
]);
|
||||
$this->mockLdap->shouldReceive('connect')->times(2)->andReturn($this->resourceId);
|
||||
$this->mockLdap->shouldReceive('setVersion')->times(2);
|
||||
$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')],
|
||||
'mail' => [$this->mockUser->email],
|
||||
'memberof' => [
|
||||
'count' => 1,
|
||||
0 => "cn=ldaptester,ou=groups,dc=example,dc=com",
|
||||
]
|
||||
]]);
|
||||
$this->mockLdap->shouldReceive('bind')->times(5)->andReturn(true);
|
||||
|
||||
$this->visit('/login')
|
||||
->see('Username')
|
||||
->type($this->mockUser->name, '#username')
|
||||
->type($this->mockUser->password, '#password')
|
||||
->press('Log In')
|
||||
->seePageIs('/');
|
||||
|
||||
$user = User::where('email', $this->mockUser->email)->first();
|
||||
$this->seeInDatabase('role_user', [
|
||||
'user_id' => $user->id,
|
||||
'role_id' => $roleToReceive->id
|
||||
]);
|
||||
$this->dontSeeInDatabase('role_user', [
|
||||
'user_id' => $user->id,
|
||||
'role_id' => $existingRole->id
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_external_auth_id_visible_in_roles_page_when_ldap_active()
|
||||
{
|
||||
$role = factory(Role::class)->create(['name' => 'ldaptester', 'external_auth_id' => 'ex-auth-a, test-second-param']);
|
||||
$this->asAdmin()->visit('/settings/roles/' . $role->id)
|
||||
->see('ex-auth-a');
|
||||
}
|
||||
|
||||
public function test_login_maps_roles_using_external_auth_ids_if_set()
|
||||
{
|
||||
$roleToReceive = factory(Role::class)->create(['name' => 'ldaptester', 'external_auth_id' => 'test-second-param, ex-auth-a']);
|
||||
$roleToNotReceive = factory(Role::class)->create(['name' => 'ldaptester-not-receive', 'display_name' => 'ex-auth-a', 'external_auth_id' => 'test-second-param']);
|
||||
|
||||
app('config')->set([
|
||||
'services.ldap.user_to_groups' => true,
|
||||
'services.ldap.group_attribute' => 'memberOf',
|
||||
'services.ldap.remove_from_groups' => true,
|
||||
]);
|
||||
$this->mockLdap->shouldReceive('connect')->times(2)->andReturn($this->resourceId);
|
||||
$this->mockLdap->shouldReceive('setVersion')->times(2);
|
||||
$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')],
|
||||
'mail' => [$this->mockUser->email],
|
||||
'memberof' => [
|
||||
'count' => 1,
|
||||
0 => "cn=ex-auth-a,ou=groups,dc=example,dc=com",
|
||||
]
|
||||
]]);
|
||||
$this->mockLdap->shouldReceive('bind')->times(5)->andReturn(true);
|
||||
|
||||
$this->visit('/login')
|
||||
->see('Username')
|
||||
->type($this->mockUser->name, '#username')
|
||||
->type($this->mockUser->password, '#password')
|
||||
->press('Log In')
|
||||
->seePageIs('/');
|
||||
|
||||
$user = User::where('email', $this->mockUser->email)->first();
|
||||
$this->seeInDatabase('role_user', [
|
||||
'user_id' => $user->id,
|
||||
'role_id' => $roleToReceive->id
|
||||
]);
|
||||
$this->dontSeeInDatabase('role_user', [
|
||||
'user_id' => $user->id,
|
||||
'role_id' => $roleToNotReceive->id
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -592,4 +592,26 @@ class RestrictionsTest extends BrowserKitTest
|
||||
->see('You do not have permission')
|
||||
->seePageIs('/');
|
||||
}
|
||||
|
||||
public function test_can_create_page_if_chapter_has_permissions_when_book_not_visible()
|
||||
{
|
||||
$book = Book::first();
|
||||
$this->setEntityRestrictions($book, []);
|
||||
$bookChapter = $book->chapters->first();
|
||||
$this->setEntityRestrictions($bookChapter, ['view']);
|
||||
|
||||
$this->actingAs($this->user)->visit($bookChapter->getUrl())
|
||||
->dontSee('New Page');
|
||||
|
||||
$this->setEntityRestrictions($bookChapter, ['view', 'create']);
|
||||
|
||||
$this->actingAs($this->user)->visit($bookChapter->getUrl())
|
||||
->click('New Page')
|
||||
->seeStatusCode(200)
|
||||
->type('test page', 'name')
|
||||
->type('test content', 'html')
|
||||
->press('Save Page')
|
||||
->seePageIs($book->getUrl('/page/test-page'))
|
||||
->seeStatusCode(200);
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,11 @@ const config = {
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: ['@babel/preset-env']
|
||||
presets: [[
|
||||
'@babel/preset-env', {
|
||||
useBuiltIns: 'usage'
|
||||
}
|
||||
]]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user