/public/bower
/storage/images
_ide_helper.php
-/storage/debugbar
\ No newline at end of file
+/storage/debugbar
+.phpstorm.meta.php
+yarn.lock
+++ /dev/null
-<?php namespace BookStack;
-
-class EmailConfirmation extends Model
-{
- protected $fillable = ['user_id', 'token'];
-
- /**
- * Get the user that this confirmation is attached to.
- * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
- */
- public function user()
- {
- return $this->belongsTo(User::class);
- }
-
-}
$exactTerms = [];
$fuzzyTerms = [];
$search = static::newQuery();
+
foreach ($terms as $key => $term) {
- $safeTerm = htmlentities($term, ENT_QUOTES);
- $safeTerm = preg_replace('/[+\-><\(\)~*\"@]+/', ' ', $safeTerm);
- if (preg_match('/".*?"/', $safeTerm) || is_numeric($safeTerm)) {
- $safeTerm = preg_replace('/^"(.*?)"$/', '$1', $term);
- $exactTerms[] = '%' . $safeTerm . '%';
+ $term = htmlentities($term, ENT_QUOTES);
+ $term = preg_replace('/[+\-><\(\)~*\"@]+/', ' ', $term);
+ if (preg_match('/".*?"/', $term) || is_numeric($term)) {
+ $term = str_replace('"', '', $term);
+ $exactTerms[] = '%' . $term . '%';
} else {
- $safeTerm = '' . $safeTerm . '*';
- if (trim($safeTerm) !== '*') $fuzzyTerms[] = $safeTerm;
+ $term = '' . $term . '*';
+ if ($term !== '*') $fuzzyTerms[] = $term;
}
}
+
$isFuzzy = count($exactTerms) === 0 || count($fuzzyTerms) > 0;
// Perform fulltext search if relevant terms exist.
}
});
}
+
$orderBy = $isFuzzy ? 'title_relevance' : 'updated_at';
// Add additional where terms
+++ /dev/null
-<?php
-
-namespace BookStack\Events;
-
-abstract class Event
-{
- //
-}
--- /dev/null
+<?php namespace BookStack\Exceptions;
+
+
+class FileUploadException extends PrettyException {}
\ No newline at end of file
} while ($e = $e->getPrevious());
return $message;
}
+
+ /**
+ * Convert an authentication exception into an unauthenticated response.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @param \Illuminate\Auth\AuthenticationException $exception
+ * @return \Illuminate\Http\Response
+ */
+ protected function unauthenticated($request, AuthenticationException $exception)
+ {
+ if ($request->expectsJson()) {
+ return response()->json(['error' => 'Unauthenticated.'], 401);
+ }
+
+ return redirect()->guest('login');
+ }
}
--- /dev/null
+<?php namespace BookStack;
+
+
+class File extends Ownable
+{
+ protected $fillable = ['name', 'order'];
+
+ /**
+ * Get the downloadable file name for this upload.
+ * @return mixed|string
+ */
+ public function getFileName()
+ {
+ if (str_contains($this->name, '.')) return $this->name;
+ return $this->name . '.' . $this->extension;
+ }
+
+ /**
+ * Get the page this file was uploaded to.
+ * @return Page
+ */
+ public function page()
+ {
+ return $this->belongsTo(Page::class, 'uploaded_to');
+ }
+
+ /**
+ * Get the url of this file.
+ * @return string
+ */
+ public function getUrl()
+ {
+ return baseUrl('/files/' . $this->id);
+ }
+
+}
--- /dev/null
+<?php
+
+namespace BookStack\Http\Controllers\Auth;
+
+use BookStack\Http\Controllers\Controller;
+use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
+use Illuminate\Http\Request;
+use Password;
+
+class ForgotPasswordController extends Controller
+{
+ /*
+ |--------------------------------------------------------------------------
+ | Password Reset Controller
+ |--------------------------------------------------------------------------
+ |
+ | This controller is responsible for handling password reset emails and
+ | includes a trait which assists in sending these notifications from
+ | your application to your users. Feel free to explore this trait.
+ |
+ */
+
+ use SendsPasswordResetEmails;
+
+ /**
+ * Create a new controller instance.
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ $this->middleware('guest');
+ parent::__construct();
+ }
+
+
+ /**
+ * Send a reset link to the given user.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @return \Illuminate\Http\RedirectResponse
+ */
+ public function sendResetLinkEmail(Request $request)
+ {
+ $this->validate($request, ['email' => 'required|email']);
+
+ // We will send the password reset link to this user. Once we have attempted
+ // to send the link, we will examine the response then see the message we
+ // need to show to the user. Finally, we'll send out a proper response.
+ $response = $this->broker()->sendResetLink(
+ $request->only('email')
+ );
+
+ if ($response === Password::RESET_LINK_SENT) {
+ $message = 'A password reset link has been sent to ' . $request->get('email') . '.';
+ session()->flash('success', $message);
+ return back()->with('status', trans($response));
+ }
+
+ // If an error was returned by the password broker, we will get this message
+ // translated so we can notify a user of the problem. We'll redirect back
+ // to where the users came from so they can attempt this process again.
+ return back()->withErrors(
+ ['email' => trans($response)]
+ );
+ }
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+namespace BookStack\Http\Controllers\Auth;
+
+use BookStack\Http\Controllers\Controller;
+use BookStack\Repos\UserRepo;
+use BookStack\Services\SocialAuthService;
+use Illuminate\Contracts\Auth\Authenticatable;
+use Illuminate\Foundation\Auth\AuthenticatesUsers;
+use Illuminate\Http\Request;
+
+class LoginController extends Controller
+{
+ /*
+ |--------------------------------------------------------------------------
+ | Login Controller
+ |--------------------------------------------------------------------------
+ |
+ | This controller handles authenticating users for the application and
+ | redirecting them to your home screen. The controller uses a trait
+ | to conveniently provide its functionality to your applications.
+ |
+ */
+
+ use AuthenticatesUsers;
+
+ /**
+ * Where to redirect users after login.
+ *
+ * @var string
+ */
+ protected $redirectTo = '/';
+
+ protected $redirectPath = '/';
+ protected $redirectAfterLogout = '/login';
+
+ protected $socialAuthService;
+ protected $userRepo;
+
+ /**
+ * Create a new controller instance.
+ *
+ * @param SocialAuthService $socialAuthService
+ * @param UserRepo $userRepo
+ */
+ public function __construct(SocialAuthService $socialAuthService, UserRepo $userRepo)
+ {
+ $this->middleware('guest', ['only' => ['getLogin', 'postLogin']]);
+ $this->socialAuthService = $socialAuthService;
+ $this->userRepo = $userRepo;
+ $this->redirectPath = baseUrl('/');
+ $this->redirectAfterLogout = baseUrl('/login');
+ parent::__construct();
+ }
+
+ public function username()
+ {
+ return config('auth.method') === 'standard' ? 'email' : 'username';
+ }
+
+ /**
+ * Overrides the action when a user is authenticated.
+ * If the user authenticated but does not exist in the user table we create them.
+ * @param Request $request
+ * @param Authenticatable $user
+ * @return \Illuminate\Http\RedirectResponse
+ * @throws AuthException
+ */
+ protected function authenticated(Request $request, Authenticatable $user)
+ {
+ // Explicitly log them out for now if they do no exist.
+ if (!$user->exists) auth()->logout($user);
+
+ if (!$user->exists && $user->email === null && !$request->has('email')) {
+ $request->flash();
+ session()->flash('request-email', true);
+ return redirect('/login');
+ }
+
+ if (!$user->exists && $user->email === null && $request->has('email')) {
+ $user->email = $request->get('email');
+ }
+
+ if (!$user->exists) {
+
+ // Check for users with same email already
+ $alreadyUser = $user->newQuery()->where('email', '=', $user->email)->count() > 0;
+ if ($alreadyUser) {
+ throw new AuthException('A user with the email ' . $user->email . ' already exists but with different credentials.');
+ }
+
+ $user->save();
+ $this->userRepo->attachDefaultRole($user);
+ auth()->login($user);
+ }
+
+ $path = session()->pull('url.intended', '/');
+ $path = baseUrl($path, true);
+ return redirect($path);
+ }
+
+ /**
+ * Show the application login form.
+ * @return \Illuminate\Http\Response
+ */
+ public function getLogin()
+ {
+ $socialDrivers = $this->socialAuthService->getActiveDrivers();
+ $authMethod = config('auth.method');
+ return view('auth/login', ['socialDrivers' => $socialDrivers, 'authMethod' => $authMethod]);
+ }
+
+ /**
+ * Redirect to the relevant social site.
+ * @param $socialDriver
+ * @return \Symfony\Component\HttpFoundation\RedirectResponse
+ */
+ public function getSocialLogin($socialDriver)
+ {
+ session()->put('social-callback', 'login');
+ return $this->socialAuthService->startLogIn($socialDriver);
+ }
+}
\ No newline at end of file
+++ /dev/null
-<?php
-
-namespace BookStack\Http\Controllers\Auth;
-
-use BookStack\Http\Controllers\Controller;
-use Illuminate\Foundation\Auth\ResetsPasswords;
-use Illuminate\Http\Request;
-use Password;
-
-class PasswordController extends Controller
-{
- /*
- |--------------------------------------------------------------------------
- | Password Reset Controller
- |--------------------------------------------------------------------------
- |
- | This controller is responsible for handling password reset requests
- | and uses a simple trait to include this behavior. You're free to
- | explore this trait and override any methods you wish to tweak.
- |
- */
-
- use ResetsPasswords;
-
- protected $redirectTo = '/';
-
- /**
- * Create a new password controller instance.
- */
- public function __construct()
- {
- $this->middleware('guest');
- }
-
-
- /**
- * Send a reset link to the given user.
- *
- * @param \Illuminate\Http\Request $request
- * @return \Illuminate\Http\Response
- */
- public function sendResetLinkEmail(Request $request)
- {
- $this->validate($request, ['email' => 'required|email']);
-
- $broker = $this->getBroker();
-
- $response = Password::broker($broker)->sendResetLink(
- $request->only('email'), $this->resetEmailBuilder()
- );
-
- switch ($response) {
- case Password::RESET_LINK_SENT:
- $message = 'A password reset link has been sent to ' . $request->get('email') . '.';
- session()->flash('success', $message);
- return $this->getSendResetLinkEmailSuccessResponse($response);
-
- case Password::INVALID_USER:
- default:
- return $this->getSendResetLinkEmailFailureResponse($response);
- }
- }
-
- /**
- * Get the response for after a successful password reset.
- *
- * @param string $response
- * @return \Symfony\Component\HttpFoundation\Response
- */
- protected function getResetSuccessResponse($response)
- {
- $message = 'Your password has been successfully reset.';
- session()->flash('success', $message);
- return redirect($this->redirectPath())->with('status', trans($response));
- }
-}
-<?php namespace BookStack\Http\Controllers\Auth;
+<?php
-use BookStack\Exceptions\AuthException;
-use Illuminate\Contracts\Auth\Authenticatable;
-use Illuminate\Http\Request;
-use BookStack\Exceptions\SocialSignInException;
+namespace BookStack\Http\Controllers\Auth;
+
+use BookStack\Exceptions\ConfirmationEmailException;
use BookStack\Exceptions\UserRegistrationException;
use BookStack\Repos\UserRepo;
use BookStack\Services\EmailConfirmationService;
use BookStack\Services\SocialAuthService;
-use BookStack\SocialAccount;
+use BookStack\User;
+use Exception;
+use Illuminate\Http\Request;
+use Illuminate\Http\Response;
use Validator;
use BookStack\Http\Controllers\Controller;
-use Illuminate\Foundation\Auth\ThrottlesLogins;
-use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
+use Illuminate\Foundation\Auth\RegistersUsers;
-class AuthController extends Controller
+class RegisterController extends Controller
{
/*
|--------------------------------------------------------------------------
- | Registration & Login Controller
+ | Register Controller
|--------------------------------------------------------------------------
|
- | This controller handles the registration of new users, as well as the
- | authentication of existing users. By default, this controller uses
- | a simple trait to add these behaviors. Why don't you explore it?
+ | This controller handles the registration of new users as well as their
+ | validation and creation. By default this controller uses a trait to
+ | provide this functionality without requiring any additional code.
|
*/
- use AuthenticatesAndRegistersUsers, ThrottlesLogins;
-
- protected $redirectPath = '/';
- protected $redirectAfterLogout = '/login';
- protected $username = 'email';
+ use RegistersUsers;
protected $socialAuthService;
protected $emailConfirmationService;
protected $userRepo;
/**
- * Create a new authentication controller instance.
+ * Where to redirect users after login / registration.
+ *
+ * @var string
+ */
+ protected $redirectTo = '/';
+ protected $redirectPath = '/';
+
+ /**
+ * Create a new controller instance.
+ *
* @param SocialAuthService $socialAuthService
* @param EmailConfirmationService $emailConfirmationService
* @param UserRepo $userRepo
*/
public function __construct(SocialAuthService $socialAuthService, EmailConfirmationService $emailConfirmationService, UserRepo $userRepo)
{
- $this->middleware('guest', ['only' => ['getLogin', 'postLogin', 'getRegister', 'postRegister']]);
+ $this->middleware('guest');
$this->socialAuthService = $socialAuthService;
$this->emailConfirmationService = $emailConfirmationService;
$this->userRepo = $userRepo;
+ $this->redirectTo = baseUrl('/');
$this->redirectPath = baseUrl('/');
- $this->redirectAfterLogout = baseUrl('/login');
- $this->username = config('auth.method') === 'standard' ? 'email' : 'username';
parent::__construct();
}
/**
* Get a validator for an incoming registration request.
+ *
* @param array $data
* @return \Illuminate\Contracts\Validation\Validator
*/
]);
}
+ /**
+ * Check whether or not registrations are allowed in the app settings.
+ * @throws UserRegistrationException
+ */
protected function checkRegistrationAllowed()
{
if (!setting('registration-enabled')) {
/**
* Show the application registration form.
- * @return \Illuminate\Http\Response
+ * @return Response
*/
public function getRegister()
{
/**
* Handle a registration request for the application.
- * @param \Illuminate\Http\Request $request
- * @return \Illuminate\Http\Response
+ * @param Request|\Illuminate\Http\Request $request
+ * @return Response
* @throws UserRegistrationException
+ * @throws \Illuminate\Foundation\Validation\ValidationException
*/
public function postRegister(Request $request)
{
return $this->registerUser($userData);
}
-
/**
- * Overrides the action when a user is authenticated.
- * If the user authenticated but does not exist in the user table we create them.
- * @param Request $request
- * @param Authenticatable $user
- * @return \Illuminate\Http\RedirectResponse
- * @throws AuthException
+ * Create a new user instance after a valid registration.
+ * @param array $data
+ * @return User
*/
- protected function authenticated(Request $request, Authenticatable $user)
+ protected function create(array $data)
{
- // Explicitly log them out for now if they do no exist.
- if (!$user->exists) auth()->logout($user);
-
- if (!$user->exists && $user->email === null && !$request->has('email')) {
- $request->flash();
- session()->flash('request-email', true);
- return redirect('/login');
- }
-
- if (!$user->exists && $user->email === null && $request->has('email')) {
- $user->email = $request->get('email');
- }
-
- if (!$user->exists) {
-
- // Check for users with same email already
- $alreadyUser = $user->newQuery()->where('email', '=', $user->email)->count() > 0;
- if ($alreadyUser) {
- throw new AuthException('A user with the email ' . $user->email . ' already exists but with different credentials.');
- }
-
- $user->save();
- $this->userRepo->attachDefaultRole($user);
- auth()->login($user);
- }
-
- $path = session()->pull('url.intended', '/');
- $path = baseUrl($path, true);
- return redirect($path);
- }
-
- /**
- * Register a new user after a registration callback.
- * @param $socialDriver
- * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
- * @throws UserRegistrationException
- */
- protected function socialRegisterCallback($socialDriver)
- {
- $socialUser = $this->socialAuthService->handleRegistrationCallback($socialDriver);
- $socialAccount = $this->socialAuthService->fillSocialAccount($socialDriver, $socialUser);
-
- // Create an array of the user data to create a new user instance
- $userData = [
- 'name' => $socialUser->getName(),
- 'email' => $socialUser->getEmail(),
- 'password' => str_random(30)
- ];
- return $this->registerUser($userData, $socialAccount);
+ return User::create([
+ 'name' => $data['name'],
+ 'email' => $data['email'],
+ 'password' => bcrypt($data['password']),
+ ]);
}
/**
* @param bool|false|SocialAccount $socialAccount
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
* @throws UserRegistrationException
- * @throws \BookStack\Exceptions\ConfirmationEmailException
+ * @throws ConfirmationEmailException
*/
protected function registerUser(array $userData, $socialAccount = false)
{
if (setting('registration-confirmation') || setting('registration-restrict')) {
$newUser->save();
- $this->emailConfirmationService->sendConfirmation($newUser);
+
+ try {
+ $this->emailConfirmationService->sendConfirmation($newUser);
+ } catch (Exception $e) {
+ session()->flash('error', trans('auth.email_confirm_send_error'));
+ }
+
return redirect('/register/confirm');
}
return view('auth/register-confirm');
}
- /**
- * View the confirmation email as a standard web page.
- * @param $token
- * @return \Illuminate\View\View
- * @throws UserRegistrationException
- */
- public function viewConfirmEmail($token)
- {
- $confirmation = $this->emailConfirmationService->getEmailConfirmationFromToken($token);
- return view('emails/email-confirmation', ['token' => $confirmation->token]);
- }
-
/**
* Confirms an email via a token and logs the user into the system.
* @param $token
$user = $confirmation->user;
$user->email_confirmed = true;
$user->save();
- auth()->login($confirmation->user);
- session()->flash('success', 'Your email has been confirmed!');
+ auth()->login($user);
+ session()->flash('success', trans('auth.email_confirm_success'));
$this->emailConfirmationService->deleteConfirmationsByUser($user);
return redirect($this->redirectPath);
}
'email' => 'required|email|exists:users,email'
]);
$user = $this->userRepo->getByEmail($request->get('email'));
- $this->emailConfirmationService->sendConfirmation($user);
- session()->flash('success', 'Confirmation email resent, Please check your inbox.');
- return redirect('/register/confirm');
- }
- /**
- * Show the application login form.
- * @return \Illuminate\Http\Response
- */
- public function getLogin()
- {
- $socialDrivers = $this->socialAuthService->getActiveDrivers();
- $authMethod = config('auth.method');
- return view('auth/login', ['socialDrivers' => $socialDrivers, 'authMethod' => $authMethod]);
- }
+ try {
+ $this->emailConfirmationService->sendConfirmation($user);
+ } catch (Exception $e) {
+ session()->flash('error', trans('auth.email_confirm_send_error'));
+ return redirect('/register/confirm');
+ }
- /**
- * Redirect to the relevant social site.
- * @param $socialDriver
- * @return \Symfony\Component\HttpFoundation\RedirectResponse
- */
- public function getSocialLogin($socialDriver)
- {
- session()->put('social-callback', 'login');
- return $this->socialAuthService->startLogIn($socialDriver);
+ $this->emailConfirmationService->sendConfirmation($user);
+ session()->flash('success', trans('auth.email_confirm_resent'));
+ return redirect('/register/confirm');
}
/**
return $this->socialAuthService->detachSocialAccount($socialDriver);
}
-}
+ /**
+ * Register a new user after a registration callback.
+ * @param $socialDriver
+ * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
+ * @throws UserRegistrationException
+ */
+ protected function socialRegisterCallback($socialDriver)
+ {
+ $socialUser = $this->socialAuthService->handleRegistrationCallback($socialDriver);
+ $socialAccount = $this->socialAuthService->fillSocialAccount($socialDriver, $socialUser);
+
+ // Create an array of the user data to create a new user instance
+ $userData = [
+ 'name' => $socialUser->getName(),
+ 'email' => $socialUser->getEmail(),
+ 'password' => str_random(30)
+ ];
+ return $this->registerUser($userData, $socialAccount);
+ }
+
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+namespace BookStack\Http\Controllers\Auth;
+
+use BookStack\Http\Controllers\Controller;
+use Illuminate\Foundation\Auth\ResetsPasswords;
+
+class ResetPasswordController extends Controller
+{
+ /*
+ |--------------------------------------------------------------------------
+ | Password Reset Controller
+ |--------------------------------------------------------------------------
+ |
+ | This controller is responsible for handling password reset requests
+ | and uses a simple trait to include this behavior. You're free to
+ | explore this trait and override any methods you wish to tweak.
+ |
+ */
+
+ use ResetsPasswords;
+
+ protected $redirectTo = '/';
+
+ /**
+ * Create a new controller instance.
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ $this->middleware('guest');
+ parent::__construct();
+ }
+
+ /**
+ * Get the response for a successful password reset.
+ *
+ * @param string $response
+ * @return \Illuminate\Http\Response
+ */
+ protected function sendResetResponse($response)
+ {
+ $message = 'Your password has been successfully reset.';
+ session()->flash('success', $message);
+ return redirect($this->redirectPath())
+ ->with('status', trans($response));
+ }
+}
\ No newline at end of file
$this->checkOwnablePermission('chapter-update', $chapter);
$chapter->fill($request->all());
$chapter->slug = $this->chapterRepo->findSuitableSlug($chapter->name, $book->id, $chapter->id);
- $chapter->updated_by = auth()->user()->id;
+ $chapter->updated_by = user()->id;
$chapter->save();
Activity::add($chapter, 'chapter_update', $book->id);
return redirect($chapter->getUrl());
namespace BookStack\Http\Controllers;
use BookStack\Ownable;
-use HttpRequestException;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Http\Exception\HttpResponseException;
+use Illuminate\Http\Request;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
-use Illuminate\Support\Facades\Auth;
-use Illuminate\Support\Facades\Session;
use BookStack\User;
abstract class Controller extends BaseController
*/
public function __construct()
{
- // Get a user instance for the current user
- $user = auth()->user();
- if (!$user) $user = User::getDefault();
+ $this->middleware(function ($request, $next) {
- // Share variables with views
- view()->share('signedIn', auth()->check());
- view()->share('currentUser', $user);
+ // Get a user instance for the current user
+ $user = user();
- // Share variables with controllers
- $this->currentUser = $user;
- $this->signedIn = auth()->check();
+ // Share variables with controllers
+ $this->currentUser = $user;
+ $this->signedIn = auth()->check();
+
+ // Share variables with views
+ view()->share('signedIn', $this->signedIn);
+ view()->share('currentUser', $user);
+
+ return $next($request);
+ });
}
/**
*/
protected function showPermissionError()
{
- Session::flash('error', trans('errors.permission'));
- $response = request()->wantsJson() ? response()->json(['error' => trans('errors.permissionJson')], 403) : redirect('/');
+ if (request()->wantsJson()) {
+ $response = response()->json(['error' => trans('errors.permissionJson')], 403);
+ } else {
+ $response = redirect('/');
+ session()->flash('error', trans('errors.permission'));
+ }
+
throw new HttpResponseException($response);
}
*/
protected function checkPermission($permissionName)
{
- if (!$this->currentUser || !$this->currentUser->can($permissionName)) {
+ if (!user() || !user()->can($permissionName)) {
$this->showPermissionError();
}
return true;
return response()->json(['message' => $messageText], $statusCode);
}
+ /**
+ * Create the response for when a request fails validation.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @param array $errors
+ * @return \Symfony\Component\HttpFoundation\Response
+ */
+ protected function buildFailedValidationResponse(Request $request, array $errors)
+ {
+ if ($request->expectsJson()) {
+ return response()->json(['validation' => $errors], 422);
+ }
+
+ return redirect()->to($this->getRedirectUrl())
+ ->withInput($request->input())
+ ->withErrors($errors, $this->errorBag());
+ }
+
}
--- /dev/null
+<?php namespace BookStack\Http\Controllers;
+
+use BookStack\Exceptions\FileUploadException;
+use BookStack\File;
+use BookStack\Repos\PageRepo;
+use BookStack\Services\FileService;
+use Illuminate\Http\Request;
+
+use BookStack\Http\Requests;
+
+class FileController extends Controller
+{
+ protected $fileService;
+ protected $file;
+ protected $pageRepo;
+
+ /**
+ * FileController constructor.
+ * @param FileService $fileService
+ * @param File $file
+ * @param PageRepo $pageRepo
+ */
+ public function __construct(FileService $fileService, File $file, PageRepo $pageRepo)
+ {
+ $this->fileService = $fileService;
+ $this->file = $file;
+ $this->pageRepo = $pageRepo;
+ }
+
+
+ /**
+ * Endpoint at which files are uploaded to.
+ * @param Request $request
+ */
+ public function upload(Request $request)
+ {
+ $this->validate($request, [
+ 'uploaded_to' => 'required|integer|exists:pages,id',
+ 'file' => 'required|file'
+ ]);
+
+ $pageId = $request->get('uploaded_to');
+ $page = $this->pageRepo->getById($pageId);
+
+ $this->checkPermission('file-create-all');
+ $this->checkOwnablePermission('page-update', $page);
+
+ $uploadedFile = $request->file('file');
+
+ try {
+ $file = $this->fileService->saveNewUpload($uploadedFile, $pageId);
+ } catch (FileUploadException $e) {
+ return response($e->getMessage(), 500);
+ }
+
+ return response()->json($file);
+ }
+
+ /**
+ * Update an uploaded file.
+ * @param int $fileId
+ * @param Request $request
+ * @return mixed
+ */
+ public function uploadUpdate($fileId, Request $request)
+ {
+ $this->validate($request, [
+ 'uploaded_to' => 'required|integer|exists:pages,id',
+ 'file' => 'required|file'
+ ]);
+
+ $pageId = $request->get('uploaded_to');
+ $page = $this->pageRepo->getById($pageId);
+ $file = $this->file->findOrFail($fileId);
+
+ $this->checkOwnablePermission('page-update', $page);
+ $this->checkOwnablePermission('file-create', $file);
+
+ if (intval($pageId) !== intval($file->uploaded_to)) {
+ return $this->jsonError('Page mismatch during attached file update');
+ }
+
+ $uploadedFile = $request->file('file');
+
+ try {
+ $file = $this->fileService->saveUpdatedUpload($uploadedFile, $file);
+ } catch (FileUploadException $e) {
+ return response($e->getMessage(), 500);
+ }
+
+ return response()->json($file);
+ }
+
+ /**
+ * Update the details of an existing file.
+ * @param $fileId
+ * @param Request $request
+ * @return File|mixed
+ */
+ public function update($fileId, Request $request)
+ {
+ $this->validate($request, [
+ 'uploaded_to' => 'required|integer|exists:pages,id',
+ 'name' => 'required|string|min:1|max:255',
+ 'link' => 'url|min:1|max:255'
+ ]);
+
+ $pageId = $request->get('uploaded_to');
+ $page = $this->pageRepo->getById($pageId);
+ $file = $this->file->findOrFail($fileId);
+
+ $this->checkOwnablePermission('page-update', $page);
+ $this->checkOwnablePermission('file-create', $file);
+
+ if (intval($pageId) !== intval($file->uploaded_to)) {
+ return $this->jsonError('Page mismatch during attachment update');
+ }
+
+ $file = $this->fileService->updateFile($file, $request->all());
+ return $file;
+ }
+
+ /**
+ * Attach a link to a page as a file.
+ * @param Request $request
+ * @return mixed
+ */
+ public function attachLink(Request $request)
+ {
+ $this->validate($request, [
+ 'uploaded_to' => 'required|integer|exists:pages,id',
+ 'name' => 'required|string|min:1|max:255',
+ 'link' => 'required|url|min:1|max:255'
+ ]);
+
+ $pageId = $request->get('uploaded_to');
+ $page = $this->pageRepo->getById($pageId);
+
+ $this->checkPermission('file-create-all');
+ $this->checkOwnablePermission('page-update', $page);
+
+ $fileName = $request->get('name');
+ $link = $request->get('link');
+ $file = $this->fileService->saveNewFromLink($fileName, $link, $pageId);
+
+ return response()->json($file);
+ }
+
+ /**
+ * Get the files for a specific page.
+ * @param $pageId
+ * @return mixed
+ */
+ public function listForPage($pageId)
+ {
+ $page = $this->pageRepo->getById($pageId);
+ $this->checkOwnablePermission('page-view', $page);
+ return response()->json($page->files);
+ }
+
+ /**
+ * Update the file sorting.
+ * @param $pageId
+ * @param Request $request
+ * @return mixed
+ */
+ public function sortForPage($pageId, Request $request)
+ {
+ $this->validate($request, [
+ 'files' => 'required|array',
+ 'files.*.id' => 'required|integer',
+ ]);
+ $page = $this->pageRepo->getById($pageId);
+ $this->checkOwnablePermission('page-update', $page);
+
+ $files = $request->get('files');
+ $this->fileService->updateFileOrderWithinPage($files, $pageId);
+ return response()->json(['message' => 'Attachment order updated']);
+ }
+
+ /**
+ * Get a file from storage.
+ * @param $fileId
+ */
+ public function get($fileId)
+ {
+ $file = $this->file->findOrFail($fileId);
+ $page = $this->pageRepo->getById($file->uploaded_to);
+ $this->checkOwnablePermission('page-view', $page);
+
+ if ($file->external) {
+ return redirect($file->path);
+ }
+
+ $fileContents = $this->fileService->getFile($file);
+ return response($fileContents, 200, [
+ 'Content-Type' => 'application/octet-stream',
+ 'Content-Disposition' => 'attachment; filename="'. $file->getFileName() .'"'
+ ]);
+ }
+
+ /**
+ * Delete a specific file in the system.
+ * @param $fileId
+ * @return mixed
+ */
+ public function delete($fileId)
+ {
+ $file = $this->file->findOrFail($fileId);
+ $this->checkOwnablePermission('file-delete', $file);
+ $this->fileService->deleteFile($file);
+ return response()->json(['message' => 'Attachment deleted']);
+ }
+}
use BookStack\Repos\PageRepo;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Views;
+use GatherContent\Htmldiff\Htmldiff;
class PageController extends Controller
{
/**
* Show the form for creating a new page.
- * @param $bookSlug
- * @param bool $chapterSlug
+ * @param string $bookSlug
+ * @param string $chapterSlug
* @return Response
* @internal param bool $pageSlug
*/
- public function create($bookSlug, $chapterSlug = false)
+ public function create($bookSlug, $chapterSlug = null)
{
$book = $this->bookRepo->getBySlug($bookSlug);
$chapter = $chapterSlug ? $this->chapterRepo->getBySlug($chapterSlug, $book->id) : null;
$parent = $chapter ? $chapter : $book;
$this->checkOwnablePermission('page-create', $parent);
+
+ // Redirect to draft edit screen if signed in
+ if ($this->signedIn) {
+ $draft = $this->pageRepo->getDraftPage($book, $chapter);
+ return redirect($draft->getUrl());
+ }
+
+ // Otherwise show edit view
$this->setPageTitle('Create New Page');
+ return view('pages/guest-create', ['parent' => $parent]);
+ }
+
+ /**
+ * Create a new page as a guest user.
+ * @param Request $request
+ * @param string $bookSlug
+ * @param string|null $chapterSlug
+ * @return mixed
+ * @throws NotFoundException
+ */
+ public function createAsGuest(Request $request, $bookSlug, $chapterSlug = null)
+ {
+ $this->validate($request, [
+ 'name' => 'required|string|max:255'
+ ]);
+
+ $book = $this->bookRepo->getBySlug($bookSlug);
+ $chapter = $chapterSlug ? $this->chapterRepo->getBySlug($chapterSlug, $book->id) : null;
+ $parent = $chapter ? $chapter : $book;
+ $this->checkOwnablePermission('page-create', $parent);
- $draft = $this->pageRepo->getDraftPage($book, $chapter);
- return redirect($draft->getUrl());
+ $page = $this->pageRepo->getDraftPage($book, $chapter);
+ $this->pageRepo->publishDraft($page, [
+ 'name' => $request->get('name'),
+ 'html' => ''
+ ]);
+ return redirect($page->getUrl('/edit'));
}
/**
* Show form to continue editing a draft page.
- * @param $bookSlug
- * @param $pageId
+ * @param string $bookSlug
+ * @param int $pageId
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function editDraft($bookSlug, $pageId)
$this->checkOwnablePermission('page-create', $book);
$this->setPageTitle('Edit Page Draft');
- return view('pages/edit', ['page' => $draft, 'book' => $book, 'isDraft' => true]);
+ $draftsEnabled = $this->signedIn;
+ return view('pages/edit', [
+ 'page' => $draft,
+ 'book' => $book,
+ 'isDraft' => true,
+ 'draftsEnabled' => $draftsEnabled
+ ]);
}
/**
* Display the specified page.
* If the page is not found via the slug the
* revisions are searched for a match.
- * @param $bookSlug
- * @param $pageSlug
+ * @param string $bookSlug
+ * @param string $pageSlug
* @return Response
*/
public function show($bookSlug, $pageSlug)
$this->checkOwnablePermission('page-view', $page);
$sidebarTree = $this->bookRepo->getChildren($book);
+ $pageNav = $this->pageRepo->getPageNav($page);
+
Views::add($page);
$this->setPageTitle($page->getShortName());
- return view('pages/show', ['page' => $page, 'book' => $book, 'current' => $page, 'sidebarTree' => $sidebarTree]);
+ return view('pages/show', ['page' => $page, 'book' => $book,
+ 'current' => $page, 'sidebarTree' => $sidebarTree, 'pageNav' => $pageNav]);
}
/**
* Get page from an ajax request.
- * @param $pageId
+ * @param int $pageId
* @return \Illuminate\Http\JsonResponse
*/
public function getPageAjax($pageId)
/**
* Show the form for editing the specified page.
- * @param $bookSlug
- * @param $pageSlug
+ * @param string $bookSlug
+ * @param string $pageSlug
* @return Response
*/
public function edit($bookSlug, $pageSlug)
if (count($warnings) > 0) session()->flash('warning', implode("\n", $warnings));
- return view('pages/edit', ['page' => $page, 'book' => $book, 'current' => $page]);
+ $draftsEnabled = $this->signedIn;
+ return view('pages/edit', [
+ 'page' => $page,
+ 'book' => $book,
+ 'current' => $page,
+ 'draftsEnabled' => $draftsEnabled
+ ]);
}
/**
* Update the specified page in storage.
* @param Request $request
- * @param $bookSlug
- * @param $pageSlug
+ * @param string $bookSlug
+ * @param string $pageSlug
* @return Response
*/
public function update(Request $request, $bookSlug, $pageSlug)
/**
* Save a draft update as a revision.
* @param Request $request
- * @param $pageId
+ * @param int $pageId
* @return \Illuminate\Http\JsonResponse
*/
public function saveDraft(Request $request, $pageId)
{
$page = $this->pageRepo->getById($pageId, true);
$this->checkOwnablePermission('page-update', $page);
+
+ if (!$this->signedIn) {
+ return response()->json([
+ 'status' => 'error',
+ 'message' => 'Guests cannot save drafts',
+ ], 500);
+ }
+
if ($page->draft) {
$draft = $this->pageRepo->updateDraftPage($page, $request->only(['name', 'html', 'markdown']));
} else {
/**
* Redirect from a special link url which
* uses the page id rather than the name.
- * @param $pageId
+ * @param int $pageId
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function redirectFromLink($pageId)
/**
* Show the deletion page for the specified page.
- * @param $bookSlug
- * @param $pageSlug
+ * @param string $bookSlug
+ * @param string $pageSlug
* @return \Illuminate\View\View
*/
public function showDelete($bookSlug, $pageSlug)
/**
* Show the deletion page for the specified page.
- * @param $bookSlug
- * @param $pageId
+ * @param string $bookSlug
+ * @param int $pageId
* @return \Illuminate\View\View
* @throws NotFoundException
*/
/**
* Remove the specified page from storage.
- * @param $bookSlug
- * @param $pageSlug
+ * @param string $bookSlug
+ * @param string $pageSlug
* @return Response
* @internal param int $id
*/
/**
* Remove the specified draft page from storage.
- * @param $bookSlug
- * @param $pageId
+ * @param string $bookSlug
+ * @param int $pageId
* @return Response
* @throws NotFoundException
*/
/**
* Shows the last revisions for this page.
- * @param $bookSlug
- * @param $pageSlug
+ * @param string $bookSlug
+ * @param string $pageSlug
* @return \Illuminate\View\View
*/
public function showRevisions($bookSlug, $pageSlug)
/**
* Shows a preview of a single revision
- * @param $bookSlug
- * @param $pageSlug
- * @param $revisionId
+ * @param string $bookSlug
+ * @param string $pageSlug
+ * @param int $revisionId
* @return \Illuminate\View\View
*/
public function showRevision($bookSlug, $pageSlug, $revisionId)
$book = $this->bookRepo->getBySlug($bookSlug);
$page = $this->pageRepo->getBySlug($pageSlug, $book->id);
$revision = $this->pageRepo->getRevisionById($revisionId);
+
$page->fill($revision->toArray());
$this->setPageTitle('Page Revision For ' . $page->getShortName());
- return view('pages/revision', ['page' => $page, 'book' => $book]);
+
+ return view('pages/revision', [
+ 'page' => $page,
+ 'book' => $book,
+ ]);
+ }
+
+ /**
+ * Shows the changes of a single revision
+ * @param string $bookSlug
+ * @param string $pageSlug
+ * @param int $revisionId
+ * @return \Illuminate\View\View
+ */
+ public function showRevisionChanges($bookSlug, $pageSlug, $revisionId)
+ {
+ $book = $this->bookRepo->getBySlug($bookSlug);
+ $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
+ $revision = $this->pageRepo->getRevisionById($revisionId);
+
+ $prev = $revision->getPrevious();
+ $prevContent = ($prev === null) ? '' : $prev->html;
+ $diff = (new Htmldiff)->diff($prevContent, $revision->html);
+
+ $page->fill($revision->toArray());
+ $this->setPageTitle('Page Revision For ' . $page->getShortName());
+
+ return view('pages/revision', [
+ 'page' => $page,
+ 'book' => $book,
+ 'diff' => $diff,
+ ]);
}
/**
* Restores a page using the content of the specified revision.
- * @param $bookSlug
- * @param $pageSlug
- * @param $revisionId
+ * @param string $bookSlug
+ * @param string $pageSlug
+ * @param int $revisionId
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function restoreRevision($bookSlug, $pageSlug, $revisionId)
/**
* Exports a page to pdf format using barryvdh/laravel-dompdf wrapper.
* https://github.com/barryvdh/laravel-dompdf
- * @param $bookSlug
- * @param $pageSlug
+ * @param string $bookSlug
+ * @param string $pageSlug
* @return \Illuminate\Http\Response
*/
public function exportPdf($bookSlug, $pageSlug)
/**
* Export a page to a self-contained HTML file.
- * @param $bookSlug
- * @param $pageSlug
+ * @param string $bookSlug
+ * @param string $pageSlug
* @return \Illuminate\Http\Response
*/
public function exportHtml($bookSlug, $pageSlug)
/**
* Export a page to a simple plaintext .txt file.
- * @param $bookSlug
- * @param $pageSlug
+ * @param string $bookSlug
+ * @param string $pageSlug
* @return \Illuminate\Http\Response
*/
public function exportPlainText($bookSlug, $pageSlug)
/**
* Show the Restrictions view.
- * @param $bookSlug
- * @param $pageSlug
+ * @param string $bookSlug
+ * @param string $pageSlug
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function showRestrict($bookSlug, $pageSlug)
/**
* Show the view to choose a new parent to move a page into.
- * @param $bookSlug
- * @param $pageSlug
+ * @param string $bookSlug
+ * @param string $pageSlug
* @return mixed
* @throws NotFoundException
*/
/**
* Does the action of moving the location of a page
- * @param $bookSlug
- * @param $pageSlug
+ * @param string $bookSlug
+ * @param string $pageSlug
* @param Request $request
* @return mixed
* @throws NotFoundException
/**
* Set the permissions for this page.
- * @param $bookSlug
- * @param $pageSlug
+ * @param string $bookSlug
+ * @param string $pageSlug
* @param Request $request
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
$this->setPageTitle('Settings');
// Get application version
- $version = false;
- if (function_exists('exec')) {
- $version = exec('git describe --always --tags ');
- }
+ $version = trim(file_get_contents(base_path('version')));
return view('settings/index', ['version' => $version]);
}
namespace BookStack\Http\Controllers;
use BookStack\Activity;
+use Exception;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
{
$this->checkPermission('users-manage');
$authMethod = config('auth.method');
- $roles = $this->userRepo->getAssignableRoles();
+ $roles = $this->userRepo->getAllRoles();
return view('users/create', ['authMethod' => $authMethod, 'roles' => $roles]);
}
// Get avatar from gravatar and save
if (!config('services.disable_services')) {
- $avatar = \Images::saveUserGravatar($user);
- $user->avatar()->associate($avatar);
- $user->save();
+ try {
+ $avatar = \Images::saveUserGravatar($user);
+ $user->avatar()->associate($avatar);
+ $user->save();
+ } catch (Exception $e) {
+ \Log::error('Failed to save user gravatar image');
+ }
+
}
return redirect('/settings/users');
return $this->currentUser->id == $id;
});
- $authMethod = config('auth.method');
-
$user = $this->user->findOrFail($id);
+
+ $authMethod = ($user->system_name) ? 'system' : config('auth.method');
+
$activeSocialDrivers = $socialAuthService->getActiveDrivers();
$this->setPageTitle('User Profile');
- $roles = $this->userRepo->getAssignableRoles();
+ $roles = $this->userRepo->getAllRoles();
return view('users/edit', ['user' => $user, 'activeSocialDrivers' => $activeSocialDrivers, 'authMethod' => $authMethod, 'roles' => $roles]);
}
/**
* Show the user delete page.
- * @param $id
+ * @param int $id
* @return \Illuminate\View\View
*/
public function delete($id)
return redirect($user->getEditUrl());
}
+ if ($user->system_name === 'public') {
+ session()->flash('error', 'You cannot delete the guest user');
+ return redirect($user->getEditUrl());
+ }
+
$this->userRepo->destroy($user);
session()->flash('success', 'User successfully removed');
/**
* The application's global HTTP middleware stack.
*
+ * These middleware are run during every request to your application.
+ *
* @var array
*/
protected $middleware = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
- \BookStack\Http\Middleware\EncryptCookies::class,
- \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
- \Illuminate\Session\Middleware\StartSession::class,
- \Illuminate\View\Middleware\ShareErrorsFromSession::class,
- \BookStack\Http\Middleware\VerifyCsrfToken::class,
+ ];
+
+ /**
+ * The application's route middleware groups.
+ *
+ * @var array
+ */
+ protected $middlewareGroups = [
+ 'web' => [
+ \BookStack\Http\Middleware\EncryptCookies::class,
+ \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
+ \Illuminate\Session\Middleware\StartSession::class,
+ \Illuminate\View\Middleware\ShareErrorsFromSession::class,
+ \BookStack\Http\Middleware\VerifyCsrfToken::class,
+ \Illuminate\Routing\Middleware\SubstituteBindings::class,
+ ],
+ 'api' => [
+ 'throttle:60,1',
+ 'bindings',
+ ],
];
/**
* @var array
*/
protected $routeMiddleware = [
+ 'can' => \Illuminate\Auth\Middleware\Authorize::class,
'auth' => \BookStack\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'guest' => \BookStack\Http\Middleware\RedirectIfAuthenticated::class,
public function handle($request, Closure $next)
{
if ($this->auth->check() && setting('registration-confirmation') && !$this->auth->user()->email_confirmed) {
- return redirect()->guest(baseUrl('/register/confirm/awaiting'));
+ return redirect(baseUrl('/register/confirm/awaiting'));
}
if ($this->auth->guest() && !setting('app-public')) {
*/
public function handle($request, Closure $next)
{
- if ($this->auth->check()) {
+ $requireConfirmation = setting('registration-confirmation');
+ if ($this->auth->check() && (!$requireConfirmation || ($requireConfirmation && $this->auth->user()->email_confirmed))) {
return redirect('/');
}
+++ /dev/null
-<?php
-
-namespace BookStack\Jobs;
-
-use Illuminate\Bus\Queueable;
-
-abstract class Job
-{
- /*
- |--------------------------------------------------------------------------
- | Queueable Jobs
- |--------------------------------------------------------------------------
- |
- | This job base class provides a central location to place any logic that
- | is shared across all of your jobs. The trait included with the class
- | provides access to the "queueOn" and "delay" queue helper methods.
- |
- */
-
- use Queueable;
-}
--- /dev/null
+<?php
+
+namespace BookStack\Notifications;
+
+use Illuminate\Notifications\Notification;
+use Illuminate\Notifications\Messages\MailMessage;
+
+class ConfirmEmail extends Notification
+{
+
+ public $token;
+
+ /**
+ * Create a new notification instance.
+ * @param string $token
+ */
+ public function __construct($token)
+ {
+ $this->token = $token;
+ }
+
+ /**
+ * Get the notification's delivery channels.
+ *
+ * @param mixed $notifiable
+ * @return array
+ */
+ public function via($notifiable)
+ {
+ return ['mail'];
+ }
+
+ /**
+ * Get the mail representation of the notification.
+ *
+ * @param mixed $notifiable
+ * @return \Illuminate\Notifications\Messages\MailMessage
+ */
+ public function toMail($notifiable)
+ {
+ $appName = ['appName' => setting('app-name')];
+ return (new MailMessage)
+ ->subject(trans('auth.email_confirm_subject', $appName))
+ ->greeting(trans('auth.email_confirm_greeting', $appName))
+ ->line(trans('auth.email_confirm_text'))
+ ->action(trans('auth.email_confirm_action'), baseUrl('/register/confirm/' . $this->token));
+ }
+
+}
--- /dev/null
+<?php
+
+namespace BookStack\Notifications;
+
+use Illuminate\Notifications\Notification;
+use Illuminate\Notifications\Messages\MailMessage;
+
+class ResetPassword extends Notification
+{
+ /**
+ * The password reset token.
+ *
+ * @var string
+ */
+ public $token;
+
+ /**
+ * Create a notification instance.
+ *
+ * @param string $token
+ */
+ public function __construct($token)
+ {
+ $this->token = $token;
+ }
+
+ /**
+ * Get the notification's channels.
+ *
+ * @param mixed $notifiable
+ * @return array|string
+ */
+ public function via($notifiable)
+ {
+ return ['mail'];
+ }
+
+ /**
+ * Build the mail representation of the notification.
+ *
+ * @return \Illuminate\Notifications\Messages\MailMessage
+ */
+ public function toMail()
+ {
+ return (new MailMessage)
+ ->line('You are receiving this email because we received a password reset request for your account.')
+ ->action('Reset Password', baseUrl('password/reset/' . $this->token))
+ ->line('If you did not request a password reset, no further action is required.');
+ }
+}
return $this->hasMany(PageRevision::class)->where('type', '=', 'version')->orderBy('created_at', 'desc');
}
+ /**
+ * Get the files attached to this page.
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ */
+ public function files()
+ {
+ return $this->hasMany(File::class, 'uploaded_to')->orderBy('order', 'asc');
+ }
+
/**
* Get the url for this page.
* @param string|bool $path
/**
* Get the url for this revision.
+ * @param null|string $path
* @return string
*/
- public function getUrl()
+ public function getUrl($path = null)
{
- return $this->page->getUrl() . '/revisions/' . $this->id;
+ $url = $this->page->getUrl() . '/revisions/' . $this->id;
+ if ($path) return $url . '/' . trim($path, '/');
+ return $url;
+ }
+
+ /**
+ * Get the previous revision for the same page if existing
+ * @return \BookStack\PageRevision|null
+ */
+ public function getPrevious()
+ {
+ if ($id = static::where('page_id', '=', $this->page_id)->where('id', '<', $this->id)->max('id')) {
+ return static::find($id);
+ }
+ return null;
}
}
--- /dev/null
+<?php
+
+namespace BookStack\Providers;
+
+use Illuminate\Support\ServiceProvider;
+use Illuminate\Support\Facades\Broadcast;
+
+class BroadcastServiceProvider extends ServiceProvider
+{
+ /**
+ * Bootstrap any application services.
+ *
+ * @return void
+ */
+ public function boot()
+ {
+// Broadcast::routes();
+//
+// /*
+// * Authenticate the user's personal channel...
+// */
+// Broadcast::channel('BookStack.User.*', function ($user, $userId) {
+// return (int) $user->id === (int) $userId;
+// });
+ }
+}
/**
* Register any other events for your application.
*
- * @param \Illuminate\Contracts\Events\Dispatcher $events
* @return void
*/
- public function boot(DispatcherContract $events)
+ public function boot()
{
- parent::boot($events);
-
- //
+ parent::boot();
}
}
<?php namespace BookStack\Providers;
-use Illuminate\Support\ServiceProvider;
+use Illuminate\Pagination\PaginationServiceProvider as IlluminatePaginationServiceProvider;
use Illuminate\Pagination\Paginator;
-class PaginationServiceProvider extends ServiceProvider
+class PaginationServiceProvider extends IlluminatePaginationServiceProvider
{
+
/**
* Register the service provider.
*
*/
public function register()
{
+ Paginator::viewFactoryResolver(function () {
+ return $this->app['view'];
+ });
+
Paginator::currentPathResolver(function () {
return baseUrl($this->app['request']->path());
});
use Illuminate\Routing\Router;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
+use Route;
class RouteServiceProvider extends ServiceProvider
{
/**
* Define your route model bindings, pattern filters, etc.
*
- * @param \Illuminate\Routing\Router $router
* @return void
*/
- public function boot(Router $router)
+ public function boot()
{
- //
-
- parent::boot($router);
+ parent::boot();
}
/**
* Define the routes for the application.
*
- * @param \Illuminate\Routing\Router $router
* @return void
*/
- public function map(Router $router)
+ public function map()
+ {
+ $this->mapWebRoutes();
+// $this->mapApiRoutes();
+ }
+ /**
+ * Define the "web" routes for the application.
+ *
+ * These routes all receive session state, CSRF protection, etc.
+ *
+ * @return void
+ */
+ protected function mapWebRoutes()
+ {
+ Route::group([
+ 'middleware' => 'web',
+ 'namespace' => $this->namespace,
+ ], function ($router) {
+ require base_path('routes/web.php');
+ });
+ }
+ /**
+ * Define the "api" routes for the application.
+ *
+ * These routes are typically stateless.
+ *
+ * @return void
+ */
+ protected function mapApiRoutes()
{
- $router->group(['namespace' => $this->namespace], function ($router) {
- require app_path('Http/routes.php');
+ Route::group([
+ 'middleware' => 'api',
+ 'namespace' => $this->namespace,
+ 'prefix' => 'api',
+ ], function ($router) {
+ require base_path('routes/api.php');
});
}
}
{
$book = $this->book->newInstance($input);
$book->slug = $this->findSuitableSlug($book->name);
- $book->created_by = auth()->user()->id;
- $book->updated_by = auth()->user()->id;
+ $book->created_by = user()->id;
+ $book->updated_by = user()->id;
$book->save();
$this->permissionService->buildJointPermissionsForEntity($book);
return $book;
{
$book->fill($input);
$book->slug = $this->findSuitableSlug($book->name, $book->id);
- $book->updated_by = auth()->user()->id;
+ $book->updated_by = user()->id;
$book->save();
$this->permissionService->buildJointPermissionsForEntity($book);
return $book;
{
$chapter = $this->chapter->newInstance($input);
$chapter->slug = $this->findSuitableSlug($chapter->name, $book->id);
- $chapter->created_by = auth()->user()->id;
- $chapter->updated_by = auth()->user()->id;
+ $chapter->created_by = user()->id;
+ $chapter->updated_by = user()->id;
$chapter = $book->chapters()->save($chapter);
$this->permissionService->buildJointPermissionsForEntity($chapter);
return $chapter;
*/
public function getUserDraftPages($count = 20, $page = 0)
{
- $user = auth()->user();
return $this->page->where('draft', '=', true)
- ->where('created_by', '=', $user->id)
+ ->where('created_by', '=', user()->id)
->orderBy('updated_at', 'desc')
->skip($count * $page)->take($count)->get();
}
use BookStack\Chapter;
use BookStack\Entity;
use BookStack\Exceptions\NotFoundException;
+use BookStack\Services\FileService;
use Carbon\Carbon;
use DOMDocument;
+use DOMXPath;
use Illuminate\Support\Str;
use BookStack\Page;
use BookStack\PageRevision;
* Get a page via a specific ID.
* @param $id
* @param bool $allowDrafts
- * @return mixed
+ * @return Page
*/
public function getById($id, $allowDrafts = false)
{
return $this->page->where('slug', '=', $slug)->where('book_id', '=', $bookId)->count();
}
- /**
- * Save a new page into the system.
- * Input validation must be done beforehand.
- * @param array $input
- * @param Book $book
- * @param int $chapterId
- * @return Page
- */
- public function saveNew(array $input, Book $book, $chapterId = null)
- {
- $page = $this->newFromInput($input);
- $page->slug = $this->findSuitableSlug($page->name, $book->id);
-
- if ($chapterId) $page->chapter_id = $chapterId;
-
- $page->html = $this->formatHtml($input['html']);
- $page->text = strip_tags($page->html);
- $page->created_by = auth()->user()->id;
- $page->updated_by = auth()->user()->id;
-
- $book->pages()->save($page);
- return $page;
- }
-
-
/**
* Publish a draft page to make it a normal page.
* Sets the slug and updates the content.
{
$page = $this->page->newInstance();
$page->name = 'New Page';
- $page->created_by = auth()->user()->id;
- $page->updated_by = auth()->user()->id;
+ $page->created_by = user()->id;
+ $page->updated_by = user()->id;
$page->draft = true;
if ($chapter) $page->chapter_id = $chapter->id;
return $page;
}
+ /**
+ * Parse te headers on the page to get a navigation menu
+ * @param Page $page
+ * @return array
+ */
+ public function getPageNav(Page $page)
+ {
+ if ($page->html == '') return null;
+ libxml_use_internal_errors(true);
+ $doc = new DOMDocument();
+ $doc->loadHTML(mb_convert_encoding($page->html, 'HTML-ENTITIES', 'UTF-8'));
+ $xPath = new DOMXPath($doc);
+ $headers = $xPath->query("//h1|//h2|//h3|//h4|//h5|//h6");
+
+ if (is_null($headers)) return null;
+
+ $tree = [];
+ foreach ($headers as $header) {
+ $text = $header->nodeValue;
+ $tree[] = [
+ 'nodeName' => strtolower($header->nodeName),
+ 'level' => intval(str_replace('h', '', $header->nodeName)),
+ 'link' => '#' . $header->getAttribute('id'),
+ 'text' => strlen($text) > 30 ? substr($text, 0, 27) . '...' : $text
+ ];
+ }
+ return $tree;
+ }
+
/**
* Formats a page's html to be tagged correctly
* within the system.
}
// Update with new details
- $userId = auth()->user()->id;
+ $userId = user()->id;
$page->fill($input);
$page->html = $this->formatHtml($input['html']);
$page->text = strip_tags($page->html);
$page->fill($revision->toArray());
$page->slug = $this->findSuitableSlug($page->name, $book->id, $page->id);
$page->text = strip_tags($page->html);
- $page->updated_by = auth()->user()->id;
+ $page->updated_by = user()->id;
$page->save();
return $page;
}
*/
public function saveRevision(Page $page, $summary = null)
{
- $revision = $this->pageRevision->fill($page->toArray());
+ $revision = $this->pageRevision->newInstance($page->toArray());
if (setting('app-editor') !== 'markdown') $revision->markdown = '';
$revision->page_id = $page->id;
$revision->slug = $page->slug;
$revision->book_slug = $page->book->slug;
- $revision->created_by = auth()->user()->id;
+ $revision->created_by = user()->id;
$revision->created_at = $page->updated_at;
$revision->type = 'version';
$revision->summary = $summary;
$revision->save();
+
// Clear old revisions
if ($this->pageRevision->where('page_id', '=', $page->id)->count() > 50) {
$this->pageRevision->where('page_id', '=', $page->id)
->orderBy('created_at', 'desc')->skip(50)->take(5)->delete();
}
+
return $revision;
}
*/
public function saveUpdateDraft(Page $page, $data = [])
{
- $userId = auth()->user()->id;
+ $userId = user()->id;
$drafts = $this->userUpdateDraftsQuery($page, $userId)->get();
if ($drafts->count() > 0) {
$query = $this->pageRevision->where('type', '=', 'update_draft')
->where('page_id', '=', $page->id)
->where('updated_at', '>', $page->updated_at)
- ->where('created_by', '!=', auth()->user()->id)
+ ->where('created_by', '!=', user()->id)
->with('createdBy');
if ($minRange !== null) {
/**
* Gets a single revision via it's id.
* @param $id
- * @return mixed
+ * @return PageRevision
*/
public function getRevisionById($id)
{
$page->revisions()->delete();
$page->permissions()->delete();
$this->permissionService->deleteJointPermissionsForEntity($page);
+
+ // Delete AttachedFiles
+ $fileService = app(FileService::class);
+ foreach ($page->files as $file) {
+ $fileService->deleteFile($file);
+ }
+
$page->delete();
}
*/
public function getAllRoles()
{
- return $this->role->where('hidden', '=', false)->get();
+ return $this->role->all();
}
/**
*/
public function getAllRolesExcept(Role $role)
{
- return $this->role->where('id', '!=', $role->id)->where('hidden', '=', false)->get();
+ return $this->role->where('id', '!=', $role->id)->get();
}
/**
{
$role = $this->role->findOrFail($roleId);
- if ($role->hidden) throw new PermissionsException("Cannot update a hidden role");
-
$permissions = isset($roleData['permissions']) ? array_keys($roleData['permissions']) : [];
$this->assignRolePermissions($role, $permissions);
use BookStack\Role;
use BookStack\User;
+use Exception;
use Setting;
class UserRepo
// Get avatar from gravatar and save
if (!config('services.disable_services')) {
- $avatar = \Images::saveUserGravatar($user);
- $user->avatar()->associate($avatar);
- $user->save();
+ try {
+ $avatar = \Images::saveUserGravatar($user);
+ $user->avatar()->associate($avatar);
+ $user->save();
+ } catch (Exception $e) {
+ $user->save();
+ \Log::error('Failed to save user gravatar image');
+ }
}
return $user;
* Get the roles in the system that are assignable to a user.
* @return mixed
*/
- public function getAssignableRoles()
+ public function getAllRoles()
{
- return $this->role->visible();
+ return $this->role->all();
}
/**
*/
public function getRestrictableRoles()
{
- return $this->role->where('hidden', '=', false)->where('system_name', '=', '')->get();
+ return $this->role->where('system_name', '!=', 'admin')->get();
}
}
\ No newline at end of file
/**
* Get the role object for the specified role.
* @param $roleName
- * @return mixed
+ * @return Role
*/
public static function getRole($roleName)
{
/**
* Get the role object for the specified system role.
* @param $roleName
- * @return mixed
+ * @return Role
*/
public static function getSystemRole($roleName)
{
{
$this->activity = $activity;
$this->permissionService = $permissionService;
- $this->user = auth()->user();
+ $this->user = user();
}
/**
<?php namespace BookStack\Services;
-
+use BookStack\Notifications\ConfirmEmail;
+use BookStack\Repos\UserRepo;
use Carbon\Carbon;
-use Illuminate\Contracts\Mail\Mailer;
-use Illuminate\Mail\Message;
-use BookStack\EmailConfirmation;
use BookStack\Exceptions\ConfirmationEmailException;
use BookStack\Exceptions\UserRegistrationException;
-use BookStack\Repos\UserRepo;
-use BookStack\Setting;
use BookStack\User;
+use Illuminate\Database\Connection as Database;
class EmailConfirmationService
{
- protected $mailer;
- protected $emailConfirmation;
+ protected $db;
+ protected $users;
/**
* EmailConfirmationService constructor.
- * @param Mailer $mailer
- * @param EmailConfirmation $emailConfirmation
+ * @param Database $db
+ * @param UserRepo $users
*/
- public function __construct(Mailer $mailer, EmailConfirmation $emailConfirmation)
+ public function __construct(Database $db, UserRepo $users)
{
- $this->mailer = $mailer;
- $this->emailConfirmation = $emailConfirmation;
+ $this->db = $db;
+ $this->users = $users;
}
/**
if ($user->email_confirmed) {
throw new ConfirmationEmailException('Email has already been confirmed, Try logging in.', '/login');
}
+
$this->deleteConfirmationsByUser($user);
+ $token = $this->createEmailConfirmation($user);
+
+ $user->notify(new ConfirmEmail($token));
+ }
+
+ /**
+ * Creates a new email confirmation in the database and returns the token.
+ * @param User $user
+ * @return string
+ */
+ public function createEmailConfirmation(User $user)
+ {
$token = $this->getToken();
- $this->emailConfirmation->create([
+ $this->db->table('email_confirmations')->insert([
'user_id' => $user->id,
- 'token' => $token,
+ 'token' => $token,
+ 'created_at' => Carbon::now(),
+ 'updated_at' => Carbon::now()
]);
- $this->mailer->send('emails/email-confirmation', ['token' => $token], function (Message $message) use ($user) {
- $appName = setting('app-name', 'BookStack');
- $message->to($user->email, $user->name)->subject('Confirm your email on ' . $appName . '.');
- });
+ return $token;
}
/**
*/
public function getEmailConfirmationFromToken($token)
{
- $emailConfirmation = $this->emailConfirmation->where('token', '=', $token)->first();
- // If not found
+ $emailConfirmation = $this->db->table('email_confirmations')->where('token', '=', $token)->first();
+
+ // If not found show error
if ($emailConfirmation === null) {
throw new UserRegistrationException('This confirmation token is not valid or has already been used, Please try registering again.', '/register');
}
// If more than a day old
- if (Carbon::now()->subDay()->gt($emailConfirmation->created_at)) {
- $this->sendConfirmation($emailConfirmation->user);
+ if (Carbon::now()->subDay()->gt(new Carbon($emailConfirmation->created_at))) {
+ $user = $this->users->getById($emailConfirmation->user_id);
+ $this->sendConfirmation($user);
throw new UserRegistrationException('The confirmation token has expired, A new confirmation email has been sent.', '/register/confirm');
}
+ $emailConfirmation->user = $this->users->getById($emailConfirmation->user_id);
return $emailConfirmation;
}
-
/**
* Delete all email confirmations that belong to a user.
* @param User $user
*/
public function deleteConfirmationsByUser(User $user)
{
- return $this->emailConfirmation->where('user_id', '=', $user->id)->delete();
+ return $this->db->table('email_confirmations')->where('user_id', '=', $user->id)->delete();
}
/**
protected function getToken()
{
$token = str_random(24);
- while ($this->emailConfirmation->where('token', '=', $token)->exists()) {
+ while ($this->db->table('email_confirmations')->where('token', '=', $token)->exists()) {
$token = str_random(25);
}
return $token;
--- /dev/null
+<?php namespace BookStack\Services;
+
+
+use BookStack\Exceptions\FileUploadException;
+use BookStack\File;
+use Exception;
+use Illuminate\Contracts\Filesystem\FileNotFoundException;
+use Illuminate\Support\Collection;
+use Symfony\Component\HttpFoundation\File\UploadedFile;
+
+class FileService extends UploadService
+{
+
+ /**
+ * Get a file from storage.
+ * @param File $file
+ * @return string
+ */
+ public function getFile(File $file)
+ {
+ $filePath = $this->getStorageBasePath() . $file->path;
+ return $this->getStorage()->get($filePath);
+ }
+
+ /**
+ * Store a new file upon user upload.
+ * @param UploadedFile $uploadedFile
+ * @param int $page_id
+ * @return File
+ * @throws FileUploadException
+ */
+ public function saveNewUpload(UploadedFile $uploadedFile, $page_id)
+ {
+ $fileName = $uploadedFile->getClientOriginalName();
+ $filePath = $this->putFileInStorage($fileName, $uploadedFile);
+ $largestExistingOrder = File::where('uploaded_to', '=', $page_id)->max('order');
+
+ $file = File::forceCreate([
+ 'name' => $fileName,
+ 'path' => $filePath,
+ 'extension' => $uploadedFile->getClientOriginalExtension(),
+ 'uploaded_to' => $page_id,
+ 'created_by' => user()->id,
+ 'updated_by' => user()->id,
+ 'order' => $largestExistingOrder + 1
+ ]);
+
+ return $file;
+ }
+
+ /**
+ * Store a upload, saving to a file and deleting any existing uploads
+ * attached to that file.
+ * @param UploadedFile $uploadedFile
+ * @param File $file
+ * @return File
+ * @throws FileUploadException
+ */
+ public function saveUpdatedUpload(UploadedFile $uploadedFile, File $file)
+ {
+ if (!$file->external) {
+ $this->deleteFileInStorage($file);
+ }
+
+ $fileName = $uploadedFile->getClientOriginalName();
+ $filePath = $this->putFileInStorage($fileName, $uploadedFile);
+
+ $file->name = $fileName;
+ $file->path = $filePath;
+ $file->external = false;
+ $file->extension = $uploadedFile->getClientOriginalExtension();
+ $file->save();
+ return $file;
+ }
+
+ /**
+ * Save a new File attachment from a given link and name.
+ * @param string $name
+ * @param string $link
+ * @param int $page_id
+ * @return File
+ */
+ public function saveNewFromLink($name, $link, $page_id)
+ {
+ $largestExistingOrder = File::where('uploaded_to', '=', $page_id)->max('order');
+ return File::forceCreate([
+ 'name' => $name,
+ 'path' => $link,
+ 'external' => true,
+ 'extension' => '',
+ 'uploaded_to' => $page_id,
+ 'created_by' => user()->id,
+ 'updated_by' => user()->id,
+ 'order' => $largestExistingOrder + 1
+ ]);
+ }
+
+ /**
+ * Get the file storage base path, amended for storage type.
+ * This allows us to keep a generic path in the database.
+ * @return string
+ */
+ private function getStorageBasePath()
+ {
+ return $this->isLocal() ? 'storage/' : '';
+ }
+
+ /**
+ * Updates the file ordering for a listing of attached files.
+ * @param array $fileList
+ * @param $pageId
+ */
+ public function updateFileOrderWithinPage($fileList, $pageId)
+ {
+ foreach ($fileList as $index => $file) {
+ File::where('uploaded_to', '=', $pageId)->where('id', '=', $file['id'])->update(['order' => $index]);
+ }
+ }
+
+
+ /**
+ * Update the details of a file.
+ * @param File $file
+ * @param $requestData
+ * @return File
+ */
+ public function updateFile(File $file, $requestData)
+ {
+ $file->name = $requestData['name'];
+ if (isset($requestData['link']) && trim($requestData['link']) !== '') {
+ $file->path = $requestData['link'];
+ if (!$file->external) {
+ $this->deleteFileInStorage($file);
+ $file->external = true;
+ }
+ }
+ $file->save();
+ return $file;
+ }
+
+ /**
+ * Delete a File from the database and storage.
+ * @param File $file
+ */
+ public function deleteFile(File $file)
+ {
+ if ($file->external) {
+ $file->delete();
+ return;
+ }
+
+ $this->deleteFileInStorage($file);
+ $file->delete();
+ }
+
+ /**
+ * Delete a file from the filesystem it sits on.
+ * Cleans any empty leftover folders.
+ * @param File $file
+ */
+ protected function deleteFileInStorage(File $file)
+ {
+ $storedFilePath = $this->getStorageBasePath() . $file->path;
+ $storage = $this->getStorage();
+ $dirPath = dirname($storedFilePath);
+
+ $storage->delete($storedFilePath);
+ if (count($storage->allFiles($dirPath)) === 0) {
+ $storage->deleteDirectory($dirPath);
+ }
+ }
+
+ /**
+ * Store a file in storage with the given filename
+ * @param $fileName
+ * @param UploadedFile $uploadedFile
+ * @return string
+ * @throws FileUploadException
+ */
+ protected function putFileInStorage($fileName, UploadedFile $uploadedFile)
+ {
+ $fileData = file_get_contents($uploadedFile->getRealPath());
+
+ $storage = $this->getStorage();
+ $fileBasePath = 'uploads/files/' . Date('Y-m-M') . '/';
+ $storageBasePath = $this->getStorageBasePath() . $fileBasePath;
+
+ $uploadFileName = $fileName;
+ while ($storage->exists($storageBasePath . $uploadFileName)) {
+ $uploadFileName = str_random(3) . $uploadFileName;
+ }
+
+ $filePath = $fileBasePath . $uploadFileName;
+ $fileStoragePath = $this->getStorageBasePath() . $filePath;
+
+ try {
+ $storage->put($fileStoragePath, $fileData);
+ } catch (Exception $e) {
+ throw new FileUploadException('File path ' . $fileStoragePath . ' could not be uploaded to. Ensure it is writable to the server.');
+ }
+ return $filePath;
+ }
+
+}
\ No newline at end of file
use Illuminate\Contracts\Filesystem\Factory as FileSystem;
use Illuminate\Contracts\Filesystem\Filesystem as FileSystemInstance;
use Illuminate\Contracts\Cache\Repository as Cache;
-use Setting;
use Symfony\Component\HttpFoundation\File\UploadedFile;
-class ImageService
+class ImageService extends UploadService
{
protected $imageTool;
- protected $fileSystem;
protected $cache;
-
- /**
- * @var FileSystemInstance
- */
- protected $storageInstance;
protected $storageUrl;
/**
public function __construct(ImageManager $imageTool, FileSystem $fileSystem, Cache $cache)
{
$this->imageTool = $imageTool;
- $this->fileSystem = $fileSystem;
$this->cache = $cache;
+ parent::__construct($fileSystem);
}
/**
if ($secureUploads) $imageName = str_random(16) . '-' . $imageName;
$imagePath = '/uploads/images/' . $type . '/' . Date('Y-m-M') . '/';
+
+ if ($this->isLocal()) $imagePath = '/public' . $imagePath;
+
while ($storage->exists($imagePath . $imageName)) {
$imageName = str_random(3) . $imageName;
}
throw new ImageUploadException('Image Path ' . $fullPath . ' is not writable by the server.');
}
+ if ($this->isLocal()) $fullPath = str_replace_first('/public', '', $fullPath);
+
$imageDetails = [
'name' => $imageName,
'path' => $fullPath,
'uploaded_to' => $uploadedTo
];
- if (auth()->user() && auth()->user()->id !== 0) {
- $userId = auth()->user()->id;
+ if (user()->id !== 0) {
+ $userId = user()->id;
$imageDetails['created_by'] = $userId;
$imageDetails['updated_by'] = $userId;
}
return $image;
}
+ /**
+ * Get the storage path, Dependant of storage type.
+ * @param Image $image
+ * @return mixed|string
+ */
+ protected function getPath(Image $image)
+ {
+ return ($this->isLocal()) ? ('public/' . $image->path) : $image->path;
+ }
+
/**
* Get the thumbnail for an image.
* If $keepRatio is true only the width will be used.
public function getThumbnail(Image $image, $width = 220, $height = 220, $keepRatio = false)
{
$thumbDirName = '/' . ($keepRatio ? 'scaled-' : 'thumbs-') . $width . '-' . $height . '/';
- $thumbFilePath = dirname($image->path) . $thumbDirName . basename($image->path);
+ $imagePath = $this->getPath($image);
+ $thumbFilePath = dirname($imagePath) . $thumbDirName . basename($imagePath);
if ($this->cache->has('images-' . $image->id . '-' . $thumbFilePath) && $this->cache->get('images-' . $thumbFilePath)) {
return $this->getPublicUrl($thumbFilePath);
}
try {
- $thumb = $this->imageTool->make($storage->get($image->path));
+ $thumb = $this->imageTool->make($storage->get($imagePath));
} catch (Exception $e) {
if ($e instanceof \ErrorException || $e instanceof NotSupportedException) {
throw new ImageUploadException('The server cannot create thumbnails. Please check you have the GD PHP extension installed.');
{
$storage = $this->getStorage();
- $imageFolder = dirname($image->path);
- $imageFileName = basename($image->path);
+ $imageFolder = dirname($this->getPath($image));
+ $imageFileName = basename($this->getPath($image));
$allImages = collect($storage->allFiles($imageFolder));
$imagesToDelete = $allImages->filter(function ($imagePath) use ($imageFileName) {
public function saveUserGravatar(User $user, $size = 500)
{
$emailHash = md5(strtolower(trim($user->email)));
- $url = 'http://www.gravatar.com/avatar/' . $emailHash . '?s=' . $size . '&d=identicon';
+ $url = 'https://www.gravatar.com/avatar/' . $emailHash . '?s=' . $size . '&d=identicon';
$imageName = str_replace(' ', '-', $user->name . '-gravatar.png');
$image = $this->saveNewFromUrl($url, 'user', $imageName);
$image->created_by = $user->id;
return $image;
}
- /**
- * Get the storage that will be used for storing images.
- * @return FileSystemInstance
- */
- private function getStorage()
- {
- if ($this->storageInstance !== null) return $this->storageInstance;
-
- $storageType = config('filesystems.default');
- $this->storageInstance = $this->fileSystem->disk($storageType);
-
- return $this->storageInstance;
- }
-
- /**
- * Check whether or not a folder is empty.
- * @param $path
- * @return int
- */
- private function isFolderEmpty($path)
- {
- $files = $this->getStorage()->files($path);
- $folders = $this->getStorage()->directories($path);
- return count($files) === 0 && count($folders) === 0;
- }
-
/**
* Gets a public facing url for an image by checking relevant environment variables.
- * @param $filePath
+ * @param string $filePath
* @return string
*/
private function getPublicUrl($filePath)
$this->storageUrl = $storageUrl;
}
+ if ($this->isLocal()) $filePath = str_replace_first('public/', '', $filePath);
+
return ($this->storageUrl == false ? rtrim(baseUrl(''), '/') : rtrim($this->storageUrl, '/')) . $filePath;
}
use BookStack\Role;
use BookStack\User;
use Illuminate\Support\Collection;
+use Illuminate\Support\Facades\Log;
class PermissionService
{
- protected $userRoles;
- protected $isAdmin;
protected $currentAction;
- protected $currentUser;
+ protected $isAdminUser;
+ protected $userRoles = false;
+ protected $currentUserModel = false;
public $book;
public $chapter;
*/
public function __construct(JointPermission $jointPermission, Book $book, Chapter $chapter, Page $page, Role $role)
{
- $this->currentUser = auth()->user();
- $userSet = $this->currentUser !== null;
- $this->userRoles = false;
- $this->isAdmin = $userSet ? $this->currentUser->hasRole('admin') : false;
- if (!$userSet) $this->currentUser = new User();
-
$this->jointPermission = $jointPermission;
$this->role = $role;
$this->book = $book;
}
- foreach ($this->currentUser->roles as $role) {
+ foreach ($this->currentUser()->roles as $role) {
$roles[] = $role->id;
}
return $roles;
*/
public function checkOwnableUserAccess(Ownable $ownable, $permission)
{
- if ($this->isAdmin) return true;
+ if ($this->isAdmin()) {
+ $this->clean();
+ return true;
+ }
+
$explodedPermission = explode('-', $permission);
$baseQuery = $ownable->where('id', '=', $ownable->id);
// Handle non entity specific jointPermissions
if (in_array($explodedPermission[0], $nonJointPermissions)) {
- $allPermission = $this->currentUser && $this->currentUser->can($permission . '-all');
- $ownPermission = $this->currentUser && $this->currentUser->can($permission . '-own');
+ $allPermission = $this->currentUser() && $this->currentUser()->can($permission . '-all');
+ $ownPermission = $this->currentUser() && $this->currentUser()->can($permission . '-own');
$this->currentAction = 'view';
- $isOwner = $this->currentUser && $this->currentUser->id === $ownable->created_by;
+ $isOwner = $this->currentUser() && $this->currentUser()->id === $ownable->created_by;
return ($allPermission || ($isOwner && $ownPermission));
}
}
- return $this->entityRestrictionQuery($baseQuery)->count() > 0;
+ $q = $this->entityRestrictionQuery($baseQuery)->count() > 0;
+ $this->clean();
+ return $q;
}
/**
*/
protected function entityRestrictionQuery($query)
{
- return $query->where(function ($parentQuery) {
+ $q = $query->where(function ($parentQuery) {
$parentQuery->whereHas('jointPermissions', function ($permissionQuery) {
$permissionQuery->whereIn('role_id', $this->getRoles())
->where('action', '=', $this->currentAction)
$query->where('has_permission', '=', true)
->orWhere(function ($query) {
$query->where('has_permission_own', '=', true)
- ->where('created_by', '=', $this->currentUser->id);
+ ->where('created_by', '=', $this->currentUser()->id);
});
});
});
});
+ $this->clean();
+ return $q;
}
/**
// Prevent drafts being visible to others.
$query = $query->where(function ($query) {
$query->where('draft', '=', false);
- if ($this->currentUser) {
+ if ($this->currentUser()) {
$query->orWhere(function ($query) {
- $query->where('draft', '=', true)->where('created_by', '=', $this->currentUser->id);
+ $query->where('draft', '=', true)->where('created_by', '=', $this->currentUser()->id);
});
}
});
*/
public function enforceEntityRestrictions($query, $action = 'view')
{
- if ($this->isAdmin) return $query;
+ if ($this->isAdmin()) {
+ $this->clean();
+ return $query;
+ }
$this->currentAction = $action;
return $this->entityRestrictionQuery($query);
}
*/
public function filterRestrictedEntityRelations($query, $tableName, $entityIdColumn, $entityTypeColumn)
{
- if ($this->isAdmin) return $query;
+ if ($this->isAdmin()) {
+ $this->clean();
+ return $query;
+ }
+
$this->currentAction = 'view';
$tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn, 'entityTypeColumn' => $entityTypeColumn];
- return $query->where(function ($query) use ($tableDetails) {
+ $q = $query->where(function ($query) use ($tableDetails) {
$query->whereExists(function ($permissionQuery) use (&$tableDetails) {
$permissionQuery->select('id')->from('joint_permissions')
->whereRaw('joint_permissions.entity_id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
->where(function ($query) {
$query->where('has_permission', '=', true)->orWhere(function ($query) {
$query->where('has_permission_own', '=', true)
- ->where('created_by', '=', $this->currentUser->id);
+ ->where('created_by', '=', $this->currentUser()->id);
});
});
});
});
-
+ return $q;
}
/**
*/
public function filterRelatedPages($query, $tableName, $entityIdColumn)
{
- if ($this->isAdmin) return $query;
+ if ($this->isAdmin()) {
+ $this->clean();
+ return $query;
+ }
+
$this->currentAction = 'view';
$tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn];
- return $query->where(function ($query) use ($tableDetails) {
+ $q = $query->where(function ($query) use ($tableDetails) {
$query->where(function ($query) use (&$tableDetails) {
$query->whereExists(function ($permissionQuery) use (&$tableDetails) {
$permissionQuery->select('id')->from('joint_permissions')
->where(function ($query) {
$query->where('has_permission', '=', true)->orWhere(function ($query) {
$query->where('has_permission_own', '=', true)
- ->where('created_by', '=', $this->currentUser->id);
+ ->where('created_by', '=', $this->currentUser()->id);
});
});
});
})->orWhere($tableDetails['entityIdColumn'], '=', 0);
});
+ $this->clean();
+ return $q;
+ }
+
+ /**
+ * Check if the current user is an admin.
+ * @return bool
+ */
+ private function isAdmin()
+ {
+ if ($this->isAdminUser === null) {
+ $this->isAdminUser = ($this->currentUser()->id !== null) ? $this->currentUser()->hasRole('admin') : false;
+ }
+
+ return $this->isAdminUser;
+ }
+
+ /**
+ * Get the current user
+ * @return User
+ */
+ private function currentUser()
+ {
+ if ($this->currentUserModel === false) {
+ $this->currentUserModel = user();
+ }
+
+ return $this->currentUserModel;
+ }
+
+ /**
+ * Clean the cached user elements.
+ */
+ private function clean()
+ {
+ $this->currentUserModel = false;
+ $this->userRoles = false;
+ $this->isAdminUser = null;
}
}
\ No newline at end of file
$socialAccount = $this->socialAccount->where('driver_id', '=', $socialId)->first();
$user = $this->userRepo->getByEmail($socialUser->getEmail());
$isLoggedIn = auth()->check();
- $currentUser = auth()->user();
+ $currentUser = user();
// When a user is not logged in and a matching SocialAccount exists,
// Simply log the user into the application.
public function detachSocialAccount($socialDriver)
{
session();
- auth()->user()->socialAccounts()->where('driver', '=', $socialDriver)->delete();
+ user()->socialAccounts()->where('driver', '=', $socialDriver)->delete();
session()->flash('success', title_case($socialDriver) . ' account successfully detached');
- return redirect(auth()->user()->getEditUrl());
+ return redirect(user()->getEditUrl());
}
}
\ No newline at end of file
--- /dev/null
+<?php namespace BookStack\Services;
+
+use Illuminate\Contracts\Filesystem\Factory as FileSystem;
+use Illuminate\Contracts\Filesystem\Filesystem as FileSystemInstance;
+
+class UploadService
+{
+
+ /**
+ * @var FileSystem
+ */
+ protected $fileSystem;
+
+ /**
+ * @var FileSystemInstance
+ */
+ protected $storageInstance;
+
+
+ /**
+ * FileService constructor.
+ * @param $fileSystem
+ */
+ public function __construct(FileSystem $fileSystem)
+ {
+ $this->fileSystem = $fileSystem;
+ }
+
+ /**
+ * Get the storage that will be used for storing images.
+ * @return FileSystemInstance
+ */
+ protected function getStorage()
+ {
+ if ($this->storageInstance !== null) return $this->storageInstance;
+
+ $storageType = config('filesystems.default');
+ $this->storageInstance = $this->fileSystem->disk($storageType);
+
+ return $this->storageInstance;
+ }
+
+
+ /**
+ * Check whether or not a folder is empty.
+ * @param $path
+ * @return bool
+ */
+ protected function isFolderEmpty($path)
+ {
+ $files = $this->getStorage()->files($path);
+ $folders = $this->getStorage()->directories($path);
+ return (count($files) === 0 && count($folders) === 0);
+ }
+
+ /**
+ * Check if using a local filesystem.
+ * @return bool
+ */
+ protected function isLocal()
+ {
+ return strtolower(config('filesystems.default')) === 'local';
+ }
+}
\ No newline at end of file
public function __construct(View $view, PermissionService $permissionService)
{
$this->view = $view;
- $this->user = auth()->user();
+ $this->user = user();
$this->permissionService = $permissionService;
}
->filterRestrictedEntityRelations($this->view, 'views', 'viewable_id', 'viewable_type');
if ($filterModel) $query = $query->where('viewable_type', '=', get_class($filterModel));
- $query = $query->where('user_id', '=', auth()->user()->id);
+ $query = $query->where('user_id', '=', user()->id);
$viewables = $query->with('viewable')->orderBy('updated_at', 'desc')
->skip($count * $page)->take($count)->get()->pluck('viewable');
<?php namespace BookStack;
+use BookStack\Notifications\ResetPassword;
use Illuminate\Auth\Authenticatable;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
+use Illuminate\Database\Eloquent\Relations\BelongsToMany;
+use Illuminate\Notifications\Notifiable;
class User extends Model implements AuthenticatableContract, CanResetPasswordContract
{
- use Authenticatable, CanResetPassword;
+ use Authenticatable, CanResetPassword, Notifiable;
/**
* The database table used by the model.
protected $permissions;
/**
- * Returns a default guest user.
+ * Returns the default public user.
+ * @return User
*/
public static function getDefault()
{
- return new static([
- 'email' => 'guest',
- 'name' => 'Guest'
- ]);
+ return static::where('system_name', '=', 'public')->first();
+ }
+
+ /**
+ * Check if the user is the default public user.
+ * @return bool
+ */
+ public function isDefault()
+ {
+ return $this->system_name === 'public';
}
/**
* The roles that belong to the user.
+ * @return BelongsToMany
*/
public function roles()
{
+ if ($this->id === 0) return ;
return $this->belongsToMany(Role::class);
}
return '';
}
+
+ /**
+ * Send the password reset notification.
+ * @param string $token
+ * @return void
+ */
+ public function sendPasswordResetNotification($token)
+ {
+ $this->notify(new ResetPassword($token));
+ }
}
*/
function versioned_asset($file = '')
{
- // Don't require css and JS assets for testing
- if (config('app.env') === 'testing') return '';
-
- static $manifest = null;
- $manifestPath = 'build/manifest.json';
-
- if (is_null($manifest) && file_exists($manifestPath)) {
- $manifest = json_decode(file_get_contents(public_path($manifestPath)), true);
- } else if (!file_exists($manifestPath)) {
- if (config('app.env') !== 'production') {
- $path = public_path($manifestPath);
- $error = "No {$path} file found, Ensure you have built the css/js assets using gulp.";
- } else {
- $error = "No {$manifestPath} file found, Ensure you are using the release version of BookStack";
- }
- throw new \Exception($error);
+ static $version = null;
+
+ if (is_null($version)) {
+ $versionFile = base_path('version');
+ $version = trim(file_get_contents($versionFile));
}
- if (isset($manifest[$file])) {
- return baseUrl($manifest[$file]);
+ $additional = '';
+ if (config('app.env') === 'development') {
+ $additional = sha1_file(public_path($file));
}
- throw new InvalidArgumentException("File {$file} not defined in asset manifest.");
+ $path = $file . '?version=' . urlencode($version) . $additional;
+ return baseUrl($path);
+}
+
+/**
+ * Helper method to get the current User.
+ * Defaults to public 'Guest' user if not logged in.
+ * @return \BookStack\User
+ */
+function user()
+{
+ return auth()->user() ?: \BookStack\User::getDefault();
}
/**
function userCan($permission, Ownable $ownable = null)
{
if ($ownable === null) {
- return auth()->user() && auth()->user()->can($permission);
+ return user() && user()->can($permission);
}
// Check permission on ownable item
*/
function setting($key, $default = false)
{
- $settingService = app('BookStack\Services\SettingService');
+ $settingService = app(\BookStack\Services\SettingService::class);
return $settingService->get($key, $default);
}
if ($isFullUrl && !$forceAppDomain) return $path;
$path = trim($path, '/');
+ // Remove non-specified domain if forced and we have a domain
if ($isFullUrl && $forceAppDomain) {
$explodedPath = explode('/', $path);
$path = implode('/', array_splice($explodedPath, 3));
{
$queryStringSections = [];
$queryData = array_merge($data, $overrideData);
-
+
// Change sorting direction is already sorted on current attribute
if (isset($overrideData['sort']) && $overrideData['sort'] === $data['sort']) {
$queryData['order'] = ($data['order'] === 'asc') ? 'desc' : 'asc';
} else {
$queryData['order'] = 'asc';
}
-
+
foreach ($queryData as $name => $value) {
$trimmedVal = trim($value);
if ($trimmedVal === '') continue;
if (count($queryStringSections) === 0) return $path;
return baseUrl($path . '?' . implode('&', $queryStringSections));
-}
\ No newline at end of file
+}
"license": "MIT",
"type": "project",
"require": {
- "php": ">=5.5.9",
- "laravel/framework": "5.2.*",
+ "php": ">=5.6.4",
+ "laravel/framework": "^5.3.4",
+ "ext-tidy": "*",
"intervention/image": "^2.3",
"laravel/socialite": "^2.0",
"barryvdh/laravel-ide-helper": "^2.1",
- "barryvdh/laravel-debugbar": "^2.0",
+ "barryvdh/laravel-debugbar": "^2.2.3",
"league/flysystem-aws-s3-v3": "^1.0",
- "barryvdh/laravel-dompdf": "0.6.*",
- "predis/predis": "^1.0"
+ "barryvdh/laravel-dompdf": "^0.7",
+ "predis/predis": "^1.1",
+ "gathercontent/htmldiff": "^0.2.1"
},
"require-dev": {
"fzaninotto/faker": "~1.4",
"mockery/mockery": "0.9.*",
- "phpunit/phpunit": "~4.0",
- "phpspec/phpspec": "~2.1",
- "symfony/dom-crawler": "~3.0",
- "symfony/css-selector": "~3.0"
+ "phpunit/phpunit": "~5.0",
+ "symfony/css-selector": "3.1.*",
+ "symfony/dom-crawler": "3.1.*"
},
"autoload": {
"classmap": [
]
},
"scripts": {
+ "post-root-package-install": [
+ "php -r \"file_exists('.env') || copy('.env.example', '.env');\""
+ ],
+ "post-create-project-cmd": [
+ "php artisan key:generate"
+ ],
"post-install-cmd": [
- "php artisan clear-compiled",
+ "Illuminate\\Foundation\\ComposerScripts::postInstall",
"php artisan optimize"
],
- "pre-update-cmd": [
- "php artisan clear-compiled"
- ],
"post-update-cmd": [
+ "Illuminate\\Foundation\\ComposerScripts::postUpdate",
"php artisan optimize"
- ],
- "post-root-package-install": [
- "php -r \"copy('.env.example', '.env');\""
- ],
- "post-create-project-cmd": [
- "php artisan key:generate"
]
},
"config": {
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
- "hash": "eb7c71e9ed116d3fd2a1d0af07f9f134",
- "content-hash": "17d2d7fc5fed682f2a290d6588538035",
+ "hash": "3124d900cfe857392a94de479f3ff6d4",
+ "content-hash": "a968767a73f77e66e865c276cf76eedf",
"packages": [
{
"name": "aws/aws-sdk-php",
- "version": "3.17.5",
+ "version": "3.19.11",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
- "reference": "1cef9b334729b3564c9aef15481a55561c54b53f"
+ "reference": "19bac3bdd7988cbf7f89d5ce8e2748d774e2cde8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/1cef9b334729b3564c9aef15481a55561c54b53f",
- "reference": "1cef9b334729b3564c9aef15481a55561c54b53f",
+ "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/19bac3bdd7988cbf7f89d5ce8e2748d774e2cde8",
+ "reference": "19bac3bdd7988cbf7f89d5ce8e2748d774e2cde8",
"shasum": ""
},
"require": {
- "guzzlehttp/guzzle": "~5.3|~6.0.1|~6.1",
+ "guzzlehttp/guzzle": "^5.3.1|^6.2.1",
"guzzlehttp/promises": "~1.0",
- "guzzlehttp/psr7": "~1.0",
+ "guzzlehttp/psr7": "~1.3.1",
"mtdowling/jmespath.php": "~2.2",
"php": ">=5.5"
},
"s3",
"sdk"
],
- "time": "2016-04-07 22:44:13"
+ "time": "2016-09-27 19:38:36"
},
{
"name": "barryvdh/laravel-debugbar",
- "version": "v2.2.0",
+ "version": "v2.3.0",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-debugbar.git",
- "reference": "13b7058d2120c8d5af7f1ada21b7c44dd87b666a"
+ "reference": "0c87981df959c7c1943abe227baf607c92f204f9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/13b7058d2120c8d5af7f1ada21b7c44dd87b666a",
- "reference": "13b7058d2120c8d5af7f1ada21b7c44dd87b666a",
+ "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/0c87981df959c7c1943abe227baf607c92f204f9",
+ "reference": "0c87981df959c7c1943abe227baf607c92f204f9",
"shasum": ""
},
"require": {
- "illuminate/support": "5.1.*|5.2.*",
- "maximebf/debugbar": "~1.11.0",
+ "illuminate/support": "5.1.*|5.2.*|5.3.*",
+ "maximebf/debugbar": "~1.13.0",
"php": ">=5.5.9",
"symfony/finder": "~2.7|~3.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.2-dev"
+ "dev-master": "2.3-dev"
}
},
"autoload": {
"profiler",
"webprofiler"
],
- "time": "2016-02-17 08:32:21"
+ "time": "2016-09-15 14:05:56"
},
{
"name": "barryvdh/laravel-dompdf",
- "version": "v0.6.1",
+ "version": "v0.7.0",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-dompdf.git",
- "reference": "b606788108833f7765801dca35455fb23ce9f869"
+ "reference": "9b8bd179262ad6b200a11edfe7b53516afcfc42a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/b606788108833f7765801dca35455fb23ce9f869",
- "reference": "b606788108833f7765801dca35455fb23ce9f869",
+ "url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/9b8bd179262ad6b200a11edfe7b53516afcfc42a",
+ "reference": "9b8bd179262ad6b200a11edfe7b53516afcfc42a",
"shasum": ""
},
"require": {
- "dompdf/dompdf": "0.6.*",
- "illuminate/support": "5.0.x|5.1.x|5.2.x",
- "php": ">=5.4.0"
+ "dompdf/dompdf": "^0.7",
+ "illuminate/support": "5.1.x|5.2.x|5.3.x",
+ "php": ">=5.5.9"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "0.6-dev"
+ "dev-master": "0.7-dev"
}
},
"autoload": {
"laravel",
"pdf"
],
- "time": "2015-12-21 19:51:22"
+ "time": "2016-08-17 08:17:33"
},
{
"name": "barryvdh/laravel-ide-helper",
- "version": "v2.1.4",
+ "version": "v2.2.1",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-ide-helper.git",
- "reference": "f1ebd847aac9a4545325d35108cafc285fe1605f"
+ "reference": "28af7cd19ca41cc0c63dd1de2b46c2b84d31c463"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/f1ebd847aac9a4545325d35108cafc285fe1605f",
- "reference": "f1ebd847aac9a4545325d35108cafc285fe1605f",
+ "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/28af7cd19ca41cc0c63dd1de2b46c2b84d31c463",
+ "reference": "28af7cd19ca41cc0c63dd1de2b46c2b84d31c463",
"shasum": ""
},
"require": {
- "illuminate/console": "5.0.x|5.1.x|5.2.x",
- "illuminate/filesystem": "5.0.x|5.1.x|5.2.x",
- "illuminate/support": "5.0.x|5.1.x|5.2.x",
+ "barryvdh/reflection-docblock": "^2.0.4",
+ "illuminate/console": "^5.0,<5.4",
+ "illuminate/filesystem": "^5.0,<5.4",
+ "illuminate/support": "^5.0,<5.4",
"php": ">=5.4.0",
- "phpdocumentor/reflection-docblock": "^2.0.4",
- "symfony/class-loader": "~2.3|~3.0"
+ "symfony/class-loader": "^2.3|^3.0"
},
"require-dev": {
- "doctrine/dbal": "~2.3"
+ "doctrine/dbal": "~2.3",
+ "phpunit/phpunit": "4.*",
+ "scrutinizer/ocular": "~1.1",
+ "squizlabs/php_codesniffer": "~2.3"
},
"suggest": {
"doctrine/dbal": "Load information from the database about models for phpdocs (~2.3)"
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.1-dev"
+ "dev-master": "2.2-dev"
}
},
"autoload": {
"phpstorm",
"sublime"
],
- "time": "2016-03-03 08:45:00"
+ "time": "2016-07-04 11:52:48"
+ },
+ {
+ "name": "barryvdh/reflection-docblock",
+ "version": "v2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/barryvdh/ReflectionDocBlock.git",
+ "reference": "3dcbd98b5d9384a5357266efba8fd29884458e5c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/barryvdh/ReflectionDocBlock/zipball/3dcbd98b5d9384a5357266efba8fd29884458e5c",
+ "reference": "3dcbd98b5d9384a5357266efba8fd29884458e5c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0,<4.5"
+ },
+ "suggest": {
+ "dflydev/markdown": "~1.0",
+ "erusev/parsedown": "~1.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Barryvdh": [
+ "src/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mike van Riel",
+ }
+ ],
+ "time": "2016-06-13 19:28:20"
},
{
"name": "classpreloader/classpreloader",
],
"time": "2015-11-09 22:51:51"
},
+ {
+ "name": "cogpowered/finediff",
+ "version": "0.3.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/cogpowered/FineDiff.git",
+ "reference": "339ddc8c3afb656efed4f2f0a80e5c3d026f8ea8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/cogpowered/FineDiff/zipball/339ddc8c3afb656efed4f2f0a80e5c3d026f8ea8",
+ "reference": "339ddc8c3afb656efed4f2f0a80e5c3d026f8ea8",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "require-dev": {
+ "mockery/mockery": "*",
+ "phpunit/phpunit": "*"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "cogpowered\\FineDiff": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Rob Crowe",
+ },
+ {
+ "name": "Raymond Hill"
+ }
+ ],
+ "description": "PHP implementation of a Fine granularity Diff engine",
+ "homepage": "https://github.com/cogpowered/FineDiff",
+ "keywords": [
+ "diff",
+ "finediff",
+ "opcode",
+ "string",
+ "text"
+ ],
+ "time": "2014-05-19 10:25:02"
+ },
{
"name": "dnoegel/php-xdg-base-dir",
"version": "0.1",
},
{
"name": "dompdf/dompdf",
- "version": "v0.6.2",
+ "version": "v0.7.0",
"source": {
"type": "git",
"url": "https://github.com/dompdf/dompdf.git",
- "reference": "cc06008f75262510ee135b8cbb14e333a309f651"
+ "reference": "5c98652b1a5beb7e3cc8ec35419b2828dd63ab14"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/dompdf/dompdf/zipball/cc06008f75262510ee135b8cbb14e333a309f651",
- "reference": "cc06008f75262510ee135b8cbb14e333a309f651",
+ "url": "https://api.github.com/repos/dompdf/dompdf/zipball/5c98652b1a5beb7e3cc8ec35419b2828dd63ab14",
+ "reference": "5c98652b1a5beb7e3cc8ec35419b2828dd63ab14",
"shasum": ""
},
"require": {
- "phenx/php-font-lib": "0.2.*"
+ "ext-dom": "*",
+ "ext-gd": "*",
+ "ext-mbstring": "*",
+ "phenx/php-font-lib": "0.4.*",
+ "phenx/php-svg-lib": "0.1.*",
+ "php": ">=5.3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "3.7.*"
},
"type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-develop": "0.7-dev"
+ }
+ },
"autoload": {
+ "psr-4": {
+ "Dompdf\\": "src/"
+ },
"classmap": [
- "include/"
+ "lib/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "LGPL"
+ "LGPL-2.1"
],
"authors": [
{
{
"name": "Brian Sweeney",
+ },
+ {
+ "name": "Gabriel Bull",
}
],
"description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter",
"homepage": "https://github.com/dompdf/dompdf",
- "time": "2015-12-07 04:07:13"
+ "time": "2016-05-11 00:36:29"
},
{
- "name": "guzzle/guzzle",
- "version": "v3.8.1",
+ "name": "gathercontent/htmldiff",
+ "version": "0.2.1",
"source": {
"type": "git",
- "url": "https://github.com/guzzle/guzzle.git",
- "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba"
+ "url": "https://github.com/gathercontent/htmldiff.git",
+ "reference": "24674a62315f64330134b4a4c5b01a7b59193c93"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/guzzle/zipball/4de0618a01b34aa1c8c33a3f13f396dcd3882eba",
- "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba",
+ "url": "https://api.github.com/repos/gathercontent/htmldiff/zipball/24674a62315f64330134b4a4c5b01a7b59193c93",
+ "reference": "24674a62315f64330134b4a4c5b01a7b59193c93",
"shasum": ""
},
"require": {
- "ext-curl": "*",
- "php": ">=5.3.3",
- "symfony/event-dispatcher": ">=2.1"
- },
- "replace": {
- "guzzle/batch": "self.version",
- "guzzle/cache": "self.version",
- "guzzle/common": "self.version",
- "guzzle/http": "self.version",
- "guzzle/inflection": "self.version",
- "guzzle/iterator": "self.version",
- "guzzle/log": "self.version",
- "guzzle/parser": "self.version",
- "guzzle/plugin": "self.version",
- "guzzle/plugin-async": "self.version",
- "guzzle/plugin-backoff": "self.version",
- "guzzle/plugin-cache": "self.version",
- "guzzle/plugin-cookie": "self.version",
- "guzzle/plugin-curlauth": "self.version",
- "guzzle/plugin-error-response": "self.version",
- "guzzle/plugin-history": "self.version",
- "guzzle/plugin-log": "self.version",
- "guzzle/plugin-md5": "self.version",
- "guzzle/plugin-mock": "self.version",
- "guzzle/plugin-oauth": "self.version",
- "guzzle/service": "self.version",
- "guzzle/stream": "self.version"
+ "cogpowered/finediff": "0.3.1",
+ "ext-tidy": "*"
},
"require-dev": {
- "doctrine/cache": "*",
- "monolog/monolog": "1.*",
- "phpunit/phpunit": "3.7.*",
- "psr/log": "1.0.*",
- "symfony/class-loader": "*",
- "zendframework/zend-cache": "<2.3",
- "zendframework/zend-log": "<2.3"
+ "phpunit/phpunit": "4.*",
+ "squizlabs/php_codesniffer": "1.*"
},
"type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.8-dev"
- }
- },
"autoload": {
"psr-0": {
- "Guzzle": "src/",
- "Guzzle\\Tests": "tests/"
+ "GatherContent\\Htmldiff": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
],
"authors": [
{
- "name": "Michael Dowling",
- "homepage": "https://github.com/mtdowling"
+ "name": "Andrew Cairns",
},
{
- "name": "Guzzle Community",
- "homepage": "https://github.com/guzzle/guzzle/contributors"
+ "name": "Mathew Chapman",
+ },
+ {
+ "name": "Peter Legierski",
}
],
- "description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients",
- "homepage": "http://guzzlephp.org/",
- "keywords": [
- "client",
- "curl",
- "framework",
- "http",
- "http client",
- "rest",
- "web service"
- ],
- "time": "2014-01-28 22:29:15"
+ "description": "Compare two HTML strings",
+ "time": "2015-04-15 15:39:46"
},
{
"name": "guzzlehttp/guzzle",
- "version": "6.2.0",
+ "version": "6.2.1",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
- "reference": "d094e337976dff9d8e2424e8485872194e768662"
+ "reference": "3f808fba627f2c5b69e2501217bf31af349c1427"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d094e337976dff9d8e2424e8485872194e768662",
- "reference": "d094e337976dff9d8e2424e8485872194e768662",
+ "url": "https://api.github.com/repos/guzzle/guzzle/zipball/3f808fba627f2c5b69e2501217bf31af349c1427",
+ "reference": "3f808fba627f2c5b69e2501217bf31af349c1427",
"shasum": ""
},
"require": {
- "guzzlehttp/promises": "~1.0",
- "guzzlehttp/psr7": "~1.1",
- "php": ">=5.5.0"
+ "guzzlehttp/promises": "^1.0",
+ "guzzlehttp/psr7": "^1.3.1",
+ "php": ">=5.5"
},
"require-dev": {
"ext-curl": "*",
- "phpunit/phpunit": "~4.0",
- "psr/log": "~1.0"
+ "phpunit/phpunit": "^4.0",
+ "psr/log": "^1.0"
},
"type": "library",
"extra": {
"rest",
"web service"
],
- "time": "2016-03-21 20:02:09"
+ "time": "2016-07-15 17:22:37"
},
{
"name": "guzzlehttp/promises",
- "version": "1.1.0",
+ "version": "1.2.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
- "reference": "bb9024c526b22f3fe6ae55a561fd70653d470aa8"
+ "reference": "c10d860e2a9595f8883527fa0021c7da9e65f579"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/promises/zipball/bb9024c526b22f3fe6ae55a561fd70653d470aa8",
- "reference": "bb9024c526b22f3fe6ae55a561fd70653d470aa8",
+ "url": "https://api.github.com/repos/guzzle/promises/zipball/c10d860e2a9595f8883527fa0021c7da9e65f579",
+ "reference": "c10d860e2a9595f8883527fa0021c7da9e65f579",
"shasum": ""
},
"require": {
"keywords": [
"promise"
],
- "time": "2016-03-08 01:15:46"
+ "time": "2016-05-18 16:56:05"
},
{
"name": "guzzlehttp/psr7",
- "version": "1.2.3",
+ "version": "1.3.1",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
- "reference": "2e89629ff057ebb49492ba08e6995d3a6a80021b"
+ "reference": "5c6447c9df362e8f8093bda8f5d8873fe5c7f65b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/psr7/zipball/2e89629ff057ebb49492ba08e6995d3a6a80021b",
- "reference": "2e89629ff057ebb49492ba08e6995d3a6a80021b",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/5c6447c9df362e8f8093bda8f5d8873fe5c7f65b",
+ "reference": "5c6447c9df362e8f8093bda8f5d8873fe5c7f65b",
"shasum": ""
},
"require": {
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.0-dev"
+ "dev-master": "1.4-dev"
}
},
"autoload": {
"stream",
"uri"
],
- "time": "2016-02-18 21:54:00"
+ "time": "2016-06-24 23:00:38"
},
{
"name": "intervention/image",
- "version": "2.3.6",
+ "version": "2.3.8",
"source": {
"type": "git",
"url": "https://github.com/Intervention/image.git",
- "reference": "e368d262887dbb2fdfaf710880571ede51e9c0e6"
+ "reference": "4064a980324f6c3bfa2bd981dfb247afa705ec3c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Intervention/image/zipball/e368d262887dbb2fdfaf710880571ede51e9c0e6",
- "reference": "e368d262887dbb2fdfaf710880571ede51e9c0e6",
+ "url": "https://api.github.com/repos/Intervention/image/zipball/4064a980324f6c3bfa2bd981dfb247afa705ec3c",
+ "reference": "4064a980324f6c3bfa2bd981dfb247afa705ec3c",
"shasum": ""
},
"require": {
"thumbnail",
"watermark"
],
- "time": "2016-02-26 18:18:19"
+ "time": "2016-09-01 17:04:03"
},
{
"name": "jakub-onderka/php-console-color",
},
{
"name": "laravel/framework",
- "version": "v5.2.29",
+ "version": "v5.3.11",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
- "reference": "e3d644eb131f18c5f3d28ff7bc678bc797091f20"
+ "reference": "ca48001b95a0543fb39fcd7219de960bbc03eaa5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/framework/zipball/e3d644eb131f18c5f3d28ff7bc678bc797091f20",
- "reference": "e3d644eb131f18c5f3d28ff7bc678bc797091f20",
+ "url": "https://api.github.com/repos/laravel/framework/zipball/ca48001b95a0543fb39fcd7219de960bbc03eaa5",
+ "reference": "ca48001b95a0543fb39fcd7219de960bbc03eaa5",
"shasum": ""
},
"require": {
"monolog/monolog": "~1.11",
"mtdowling/cron-expression": "~1.0",
"nesbot/carbon": "~1.20",
- "paragonie/random_compat": "~1.4",
- "php": ">=5.5.9",
+ "paragonie/random_compat": "~1.4|~2.0",
+ "php": ">=5.6.4",
"psy/psysh": "0.7.*",
+ "ramsey/uuid": "~3.0",
"swiftmailer/swiftmailer": "~5.1",
- "symfony/console": "2.8.*|3.0.*",
- "symfony/debug": "2.8.*|3.0.*",
- "symfony/finder": "2.8.*|3.0.*",
- "symfony/http-foundation": "2.8.*|3.0.*",
- "symfony/http-kernel": "2.8.*|3.0.*",
- "symfony/polyfill-php56": "~1.0",
- "symfony/process": "2.8.*|3.0.*",
- "symfony/routing": "2.8.*|3.0.*",
- "symfony/translation": "2.8.*|3.0.*",
- "symfony/var-dumper": "2.8.*|3.0.*",
+ "symfony/console": "3.1.*",
+ "symfony/debug": "3.1.*",
+ "symfony/finder": "3.1.*",
+ "symfony/http-foundation": "3.1.*",
+ "symfony/http-kernel": "3.1.*",
+ "symfony/process": "3.1.*",
+ "symfony/routing": "3.1.*",
+ "symfony/translation": "3.1.*",
+ "symfony/var-dumper": "3.1.*",
"vlucas/phpdotenv": "~2.2"
},
"replace": {
"illuminate/http": "self.version",
"illuminate/log": "self.version",
"illuminate/mail": "self.version",
+ "illuminate/notifications": "self.version",
"illuminate/pagination": "self.version",
"illuminate/pipeline": "self.version",
"illuminate/queue": "self.version",
"illuminate/support": "self.version",
"illuminate/translation": "self.version",
"illuminate/validation": "self.version",
- "illuminate/view": "self.version"
+ "illuminate/view": "self.version",
+ "tightenco/collect": "self.version"
},
"require-dev": {
"aws/aws-sdk-php": "~3.0",
- "mockery/mockery": "~0.9.2",
+ "mockery/mockery": "~0.9.4",
"pda/pheanstalk": "~3.0",
- "phpunit/phpunit": "~4.1",
+ "phpunit/phpunit": "~5.4",
"predis/predis": "~1.0",
- "symfony/css-selector": "2.8.*|3.0.*",
- "symfony/dom-crawler": "2.8.*|3.0.*"
+ "symfony/css-selector": "3.1.*",
+ "symfony/dom-crawler": "3.1.*"
},
"suggest": {
"aws/aws-sdk-php": "Required to use the SQS queue driver and SES mail driver (~3.0).",
"pda/pheanstalk": "Required to use the beanstalk queue driver (~3.0).",
"predis/predis": "Required to use the redis cache and queue drivers (~1.0).",
"pusher/pusher-php-server": "Required to use the Pusher broadcast driver (~2.0).",
- "symfony/css-selector": "Required to use some of the crawler integration testing tools (2.8.*|3.0.*).",
- "symfony/dom-crawler": "Required to use most of the crawler integration testing tools (2.8.*|3.0.*)."
+ "symfony/css-selector": "Required to use some of the crawler integration testing tools (3.1.*).",
+ "symfony/dom-crawler": "Required to use most of the crawler integration testing tools (3.1.*).",
+ "symfony/psr-http-message-bridge": "Required to psr7 bridging features (0.2.*)."
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "5.2-dev"
+ "dev-master": "5.3-dev"
}
},
"autoload": {
- "classmap": [
- "src/Illuminate/Queue/IlluminateQueueClosure.php"
- ],
"files": [
"src/Illuminate/Foundation/helpers.php",
"src/Illuminate/Support/helpers.php"
"authors": [
{
"name": "Taylor Otwell",
- "email": "taylorotwell@gmail.com"
+ "email": "taylor@laravel.com"
}
],
"description": "The Laravel Framework.",
- "homepage": "http://laravel.com",
+ "homepage": "https://laravel.com",
"keywords": [
"framework",
"laravel"
],
- "time": "2016-04-03 01:43:55"
+ "time": "2016-09-28 02:15:37"
},
{
"name": "laravel/socialite",
- "version": "v2.0.15",
+ "version": "v2.0.18",
"source": {
"type": "git",
"url": "https://github.com/laravel/socialite.git",
- "reference": "edd00ab96933e3ef053533cce81e958fb26921af"
+ "reference": "76ee5397fcdea5a062361392abca4eb397e519a3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/socialite/zipball/edd00ab96933e3ef053533cce81e958fb26921af",
- "reference": "edd00ab96933e3ef053533cce81e958fb26921af",
+ "url": "https://api.github.com/repos/laravel/socialite/zipball/76ee5397fcdea5a062361392abca4eb397e519a3",
+ "reference": "76ee5397fcdea5a062361392abca4eb397e519a3",
"shasum": ""
},
"require": {
},
"require-dev": {
"mockery/mockery": "~0.9",
- "phpunit/phpunit": "~4.0"
+ "phpunit/phpunit": "~4.0|~5.0"
},
"type": "library",
"extra": {
"laravel",
"oauth"
],
- "time": "2016-03-21 14:30:30"
+ "time": "2016-06-22 12:40:16"
},
{
"name": "league/flysystem",
- "version": "1.0.20",
+ "version": "1.0.27",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem.git",
- "reference": "e87a786e3ae12a25cf78a71bb07b4b384bfaa83a"
+ "reference": "50e2045ed70a7e75a5e30bc3662904f3b67af8a9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/e87a786e3ae12a25cf78a71bb07b4b384bfaa83a",
- "reference": "e87a786e3ae12a25cf78a71bb07b4b384bfaa83a",
+ "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/50e2045ed70a7e75a5e30bc3662904f3b67af8a9",
+ "reference": "50e2045ed70a7e75a5e30bc3662904f3b67af8a9",
"shasum": ""
},
"require": {
"ext-fileinfo": "*",
"mockery/mockery": "~0.9",
"phpspec/phpspec": "^2.2",
- "phpunit/phpunit": "~4.8 || ~5.0"
+ "phpunit/phpunit": "~4.8"
},
"suggest": {
"ext-fileinfo": "Required for MimeType",
"sftp",
"storage"
],
- "time": "2016-03-14 21:54:11"
+ "time": "2016-08-10 08:55:11"
},
{
"name": "league/flysystem-aws-s3-v3",
- "version": "1.0.9",
+ "version": "1.0.13",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git",
- "reference": "595e24678bf78f8107ebc9355d8376ae0eb712c6"
+ "reference": "dc56a8faf3aff0841f9eae04b6af94a50657896c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/595e24678bf78f8107ebc9355d8376ae0eb712c6",
- "reference": "595e24678bf78f8107ebc9355d8376ae0eb712c6",
+ "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/dc56a8faf3aff0841f9eae04b6af94a50657896c",
+ "reference": "dc56a8faf3aff0841f9eae04b6af94a50657896c",
"shasum": ""
},
"require": {
}
],
"description": "Flysystem adapter for the AWS S3 SDK v3.x",
- "time": "2015-11-19 08:44:16"
+ "time": "2016-06-21 21:34:35"
},
{
"name": "league/oauth1-client",
- "version": "1.6.1",
+ "version": "1.7.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/oauth1-client.git",
- "reference": "cef3ceda13c78f89c323e4d5e6301c0eb7cea422"
+ "reference": "fca5f160650cb74d23fc11aa570dd61f86dcf647"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/cef3ceda13c78f89c323e4d5e6301c0eb7cea422",
- "reference": "cef3ceda13c78f89c323e4d5e6301c0eb7cea422",
+ "url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/fca5f160650cb74d23fc11aa570dd61f86dcf647",
+ "reference": "fca5f160650cb74d23fc11aa570dd61f86dcf647",
"shasum": ""
},
"require": {
- "guzzle/guzzle": "3.*",
- "php": ">=5.3.0"
+ "guzzlehttp/guzzle": "^6.0",
+ "php": ">=5.5.0"
},
"require-dev": {
- "mockery/mockery": "~0.9",
- "phpunit/phpunit": "~4.0",
- "squizlabs/php_codesniffer": "~2.0"
+ "mockery/mockery": "^0.9",
+ "phpunit/phpunit": "^4.0",
+ "squizlabs/php_codesniffer": "^2.0"
},
"type": "library",
"extra": {
"tumblr",
"twitter"
],
- "time": "2015-10-23 04:02:07"
+ "time": "2016-08-17 00:36:58"
},
{
"name": "maximebf/debugbar",
- "version": "v1.11.1",
+ "version": "v1.13.0",
"source": {
"type": "git",
"url": "https://github.com/maximebf/php-debugbar.git",
- "reference": "d9302891c1f0a0ac5a4f66725163a00537c6359f"
+ "reference": "5f49a5ed6cfde81d31d89378806670d77462526e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/d9302891c1f0a0ac5a4f66725163a00537c6359f",
- "reference": "d9302891c1f0a0ac5a4f66725163a00537c6359f",
+ "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/5f49a5ed6cfde81d31d89378806670d77462526e",
+ "reference": "5f49a5ed6cfde81d31d89378806670d77462526e",
"shasum": ""
},
"require": {
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.11-dev"
+ "dev-master": "1.13-dev"
}
},
"autoload": {
"debug",
"debugbar"
],
- "time": "2016-01-22 12:22:23"
+ "time": "2016-09-15 14:01:59"
},
{
"name": "monolog/monolog",
- "version": "1.18.2",
+ "version": "1.21.0",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/monolog.git",
- "reference": "064b38c16790249488e7a8b987acf1c9d7383c09"
+ "reference": "f42fbdfd53e306bda545845e4dbfd3e72edb4952"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Seldaek/monolog/zipball/064b38c16790249488e7a8b987acf1c9d7383c09",
- "reference": "064b38c16790249488e7a8b987acf1c9d7383c09",
+ "url": "https://api.github.com/repos/Seldaek/monolog/zipball/f42fbdfd53e306bda545845e4dbfd3e72edb4952",
+ "reference": "f42fbdfd53e306bda545845e4dbfd3e72edb4952",
"shasum": ""
},
"require": {
"php-console/php-console": "^3.1.3",
"phpunit/phpunit": "~4.5",
"phpunit/phpunit-mock-objects": "2.3.0",
- "raven/raven": "^0.13",
"ruflin/elastica": ">=0.90 <3.0",
+ "sentry/sentry": "^0.13",
"swiftmailer/swiftmailer": "~5.3"
},
"suggest": {
"mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
"php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
"php-console/php-console": "Allow sending log messages to Google Chrome",
- "raven/raven": "Allow sending log messages to a Sentry server",
"rollbar/rollbar": "Allow sending log messages to Rollbar",
- "ruflin/elastica": "Allow sending log messages to an Elastic Search server"
+ "ruflin/elastica": "Allow sending log messages to an Elastic Search server",
+ "sentry/sentry": "Allow sending log messages to a Sentry server"
},
"type": "library",
"extra": {
"logging",
"psr-3"
],
- "time": "2016-04-02 13:12:58"
+ "time": "2016-07-29 03:23:52"
},
{
"name": "mtdowling/cron-expression",
},
{
"name": "nikic/php-parser",
- "version": "v2.0.1",
+ "version": "v2.1.1",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
- "reference": "ce5be709d59b32dd8a88c80259028759991a4206"
+ "reference": "4dd659edadffdc2143e4753df655d866dbfeedf0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ce5be709d59b32dd8a88c80259028759991a4206",
- "reference": "ce5be709d59b32dd8a88c80259028759991a4206",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4dd659edadffdc2143e4753df655d866dbfeedf0",
+ "reference": "4dd659edadffdc2143e4753df655d866dbfeedf0",
"shasum": ""
},
"require": {
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.0-dev"
+ "dev-master": "2.1-dev"
}
},
"autoload": {
"parser",
"php"
],
- "time": "2016-02-28 19:48:28"
+ "time": "2016-09-16 12:04:44"
},
{
"name": "paragonie/random_compat",
- "version": "v1.4.1",
+ "version": "v2.0.2",
"source": {
"type": "git",
"url": "https://github.com/paragonie/random_compat.git",
- "reference": "c7e26a21ba357863de030f0b9e701c7d04593774"
+ "reference": "088c04e2f261c33bed6ca5245491cfca69195ccf"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/paragonie/random_compat/zipball/c7e26a21ba357863de030f0b9e701c7d04593774",
- "reference": "c7e26a21ba357863de030f0b9e701c7d04593774",
+ "url": "https://api.github.com/repos/paragonie/random_compat/zipball/088c04e2f261c33bed6ca5245491cfca69195ccf",
+ "reference": "088c04e2f261c33bed6ca5245491cfca69195ccf",
"shasum": ""
},
"require": {
"pseudorandom",
"random"
],
- "time": "2016-03-18 20:34:03"
+ "time": "2016-04-03 06:00:07"
},
{
"name": "phenx/php-font-lib",
- "version": "0.2.2",
+ "version": "0.4",
"source": {
"type": "git",
"url": "https://github.com/PhenX/php-font-lib.git",
- "reference": "c30c7fc00a6b0d863e9bb4c5d5dd015298b2dc82"
+ "reference": "b8af0cacdc3cbf1e41a586fcb78f506f4121a088"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PhenX/php-font-lib/zipball/c30c7fc00a6b0d863e9bb4c5d5dd015298b2dc82",
- "reference": "c30c7fc00a6b0d863e9bb4c5d5dd015298b2dc82",
+ "url": "https://api.github.com/repos/PhenX/php-font-lib/zipball/b8af0cacdc3cbf1e41a586fcb78f506f4121a088",
+ "reference": "b8af0cacdc3cbf1e41a586fcb78f506f4121a088",
"shasum": ""
},
"type": "library",
"autoload": {
- "classmap": [
- "classes/"
- ]
+ "psr-0": {
+ "FontLib\\": "src/"
+ }
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "LGPL"
+ "LGPL-3.0"
],
"authors": [
{
],
"description": "A library to read, parse, export and make subsets of different types of font files.",
"homepage": "https://github.com/PhenX/php-font-lib",
- "time": "2014-02-01 15:22:28"
+ "time": "2015-05-06 20:02:39"
},
{
- "name": "phpdocumentor/reflection-docblock",
- "version": "2.0.4",
+ "name": "phenx/php-svg-lib",
+ "version": "0.1",
"source": {
"type": "git",
- "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
- "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8"
+ "url": "https://github.com/PhenX/php-svg-lib.git",
+ "reference": "b419766515b3426c6da74b0e29e93d71c4f17099"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8",
- "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8",
+ "url": "https://api.github.com/repos/PhenX/php-svg-lib/zipball/b419766515b3426c6da74b0e29e93d71c4f17099",
+ "reference": "b419766515b3426c6da74b0e29e93d71c4f17099",
"shasum": ""
},
- "require": {
- "php": ">=5.3.3"
- },
- "require-dev": {
- "phpunit/phpunit": "~4.0"
- },
- "suggest": {
- "dflydev/markdown": "~1.0",
- "erusev/parsedown": "~1.0"
- },
"type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "2.0.x-dev"
- }
- },
"autoload": {
"psr-0": {
- "phpDocumentor": [
- "src/"
- ]
+ "Svg\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "MIT"
+ "LGPL-3.0"
],
"authors": [
{
- "name": "Mike van Riel",
- "email": "mike.vanriel@naenius.com"
+ "name": "Fabien Ménager",
+ "email": "fabien.menager@gmail.com"
}
],
- "time": "2015-02-03 12:10:50"
+ "description": "A library to read, parse and export to PDF SVG files.",
+ "homepage": "https://github.com/PhenX/php-svg-lib",
+ "time": "2015-05-06 18:49:49"
},
{
"name": "predis/predis",
- "version": "v1.0.3",
+ "version": "v1.1.1",
"source": {
"type": "git",
"url": "https://github.com/nrk/predis.git",
- "reference": "84060b9034d756b4d79641667d7f9efe1aeb8e04"
+ "reference": "f0210e38881631afeafb56ab43405a92cafd9fd1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nrk/predis/zipball/84060b9034d756b4d79641667d7f9efe1aeb8e04",
- "reference": "84060b9034d756b4d79641667d7f9efe1aeb8e04",
+ "url": "https://api.github.com/repos/nrk/predis/zipball/f0210e38881631afeafb56ab43405a92cafd9fd1",
+ "reference": "f0210e38881631afeafb56ab43405a92cafd9fd1",
"shasum": ""
},
"require": {
- "php": ">=5.3.2"
+ "php": ">=5.3.9"
},
"require-dev": {
- "phpunit/phpunit": "~4.0"
+ "phpunit/phpunit": "~4.8"
},
"suggest": {
"ext-curl": "Allows access to Webdis when paired with phpiredis",
"homepage": "http://clorophilla.net"
}
],
- "description": "Flexible and feature-complete PHP client library for Redis",
+ "description": "Flexible and feature-complete Redis client for PHP and HHVM",
"homepage": "http://github.com/nrk/predis",
"keywords": [
"nosql",
"predis",
"redis"
],
- "time": "2015-07-30 18:34:15"
+ "time": "2016-06-16 16:22:20"
},
{
"name": "psr/http-message",
- "version": "1.0",
+ "version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-message.git",
- "reference": "85d63699f0dbedb190bbd4b0d2b9dc707ea4c298"
+ "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-fig/http-message/zipball/85d63699f0dbedb190bbd4b0d2b9dc707ea4c298",
- "reference": "85d63699f0dbedb190bbd4b0d2b9dc707ea4c298",
+ "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
+ "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
"shasum": ""
},
"require": {
}
],
"description": "Common interface for HTTP messages",
+ "homepage": "https://github.com/php-fig/http-message",
"keywords": [
"http",
"http-message",
"request",
"response"
],
- "time": "2015-05-04 20:22:00"
+ "time": "2016-08-06 14:39:51"
},
{
"name": "psr/log",
- "version": "1.0.0",
+ "version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
- "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b"
+ "reference": "5277094ed527a1c4477177d102fe4c53551953e0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b",
- "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/5277094ed527a1c4477177d102fe4c53551953e0",
+ "reference": "5277094ed527a1c4477177d102fe4c53551953e0",
"shasum": ""
},
+ "require": {
+ "php": ">=5.3.0"
+ },
"type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
"autoload": {
- "psr-0": {
- "Psr\\Log\\": ""
+ "psr-4": {
+ "Psr\\Log\\": "Psr/Log/"
}
},
"notification-url": "https://packagist.org/downloads/",
}
],
"description": "Common interface for logging libraries",
+ "homepage": "https://github.com/php-fig/log",
"keywords": [
"log",
"psr",
"psr-3"
],
- "time": "2012-12-21 11:40:51"
+ "time": "2016-09-19 16:02:08"
},
{
"name": "psy/psysh",
],
"time": "2016-03-09 05:03:14"
},
+ {
+ "name": "ramsey/uuid",
+ "version": "3.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ramsey/uuid.git",
+ "reference": "a6d15c8618ea3951fd54d34e326b68d3d0bc0786"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ramsey/uuid/zipball/a6d15c8618ea3951fd54d34e326b68d3d0bc0786",
+ "reference": "a6d15c8618ea3951fd54d34e326b68d3d0bc0786",
+ "shasum": ""
+ },
+ "require": {
+ "paragonie/random_compat": "^1.0|^2.0",
+ "php": ">=5.4"
+ },
+ "replace": {
+ "rhumsaa/uuid": "self.version"
+ },
+ "require-dev": {
+ "apigen/apigen": "^4.1",
+ "codeception/aspect-mock": "1.0.0",
+ "goaop/framework": "1.0.0-alpha.2",
+ "ircmaxell/random-lib": "^1.1",
+ "jakub-onderka/php-parallel-lint": "^0.9.0",
+ "mockery/mockery": "^0.9.4",
+ "moontoast/math": "^1.1",
+ "phpunit/phpunit": "^4.7|>=5.0 <5.4",
+ "satooshi/php-coveralls": "^0.6.1",
+ "squizlabs/php_codesniffer": "^2.3"
+ },
+ "suggest": {
+ "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator",
+ "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator",
+ "ircmaxell/random-lib": "Provides RandomLib for use with the RandomLibAdapter",
+ "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).",
+ "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid",
+ "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type."
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Ramsey\\Uuid\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Marijn Huizendveld",
+ },
+ {
+ "name": "Thibaud Fabre",
+ },
+ {
+ "name": "Ben Ramsey",
+ "homepage": "https://benramsey.com"
+ }
+ ],
+ "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).",
+ "homepage": "https://github.com/ramsey/uuid",
+ "keywords": [
+ "guid",
+ "identifier",
+ "uuid"
+ ],
+ "time": "2016-08-02 18:39:32"
+ },
{
"name": "swiftmailer/swiftmailer",
- "version": "v5.4.1",
+ "version": "v5.4.3",
"source": {
"type": "git",
"url": "https://github.com/swiftmailer/swiftmailer.git",
- "reference": "0697e6aa65c83edf97bb0f23d8763f94e3f11421"
+ "reference": "4cc92842069c2bbc1f28daaaf1d2576ec4dfe153"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/0697e6aa65c83edf97bb0f23d8763f94e3f11421",
- "reference": "0697e6aa65c83edf97bb0f23d8763f94e3f11421",
+ "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/4cc92842069c2bbc1f28daaaf1d2576ec4dfe153",
+ "reference": "4cc92842069c2bbc1f28daaaf1d2576ec4dfe153",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
- "mockery/mockery": "~0.9.1,<0.9.4"
+ "mockery/mockery": "~0.9.1"
},
"type": "library",
"extra": {
"mail",
"mailer"
],
- "time": "2015-06-06 14:19:39"
+ "time": "2016-07-08 11:51:25"
},
{
"name": "symfony/class-loader",
- "version": "v3.0.4",
+ "version": "v3.1.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/class-loader.git",
- "reference": "cbb7e6a9c0213a0cffa5d9065ee8214ca4e83877"
+ "reference": "2d0ba77c46ecc96a6641009a98f72632216811ba"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/class-loader/zipball/cbb7e6a9c0213a0cffa5d9065ee8214ca4e83877",
- "reference": "cbb7e6a9c0213a0cffa5d9065ee8214ca4e83877",
+ "url": "https://api.github.com/repos/symfony/class-loader/zipball/2d0ba77c46ecc96a6641009a98f72632216811ba",
+ "reference": "2d0ba77c46ecc96a6641009a98f72632216811ba",
"shasum": ""
},
"require": {
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.0-dev"
+ "dev-master": "3.1-dev"
}
},
"autoload": {
],
"description": "Symfony ClassLoader Component",
"homepage": "https://symfony.com",
- "time": "2016-03-30 10:41:14"
+ "time": "2016-08-23 13:39:15"
},
{
"name": "symfony/console",
- "version": "v3.0.4",
+ "version": "v3.1.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "6b1175135bc2a74c08a28d89761272de8beed8cd"
+ "reference": "8ea494c34f0f772c3954b5fbe00bffc5a435e563"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/6b1175135bc2a74c08a28d89761272de8beed8cd",
- "reference": "6b1175135bc2a74c08a28d89761272de8beed8cd",
+ "url": "https://api.github.com/repos/symfony/console/zipball/8ea494c34f0f772c3954b5fbe00bffc5a435e563",
+ "reference": "8ea494c34f0f772c3954b5fbe00bffc5a435e563",
"shasum": ""
},
"require": {
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.0-dev"
+ "dev-master": "3.1-dev"
}
},
"autoload": {
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
- "time": "2016-03-16 17:00:50"
+ "time": "2016-08-19 06:48:39"
},
{
"name": "symfony/debug",
- "version": "v3.0.4",
+ "version": "v3.1.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/debug.git",
- "reference": "a06d10888a45afd97534506afb058ec38d9ba35b"
+ "reference": "34f6ac18c2974ca5fce68adf419ee7d15def6f11"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/debug/zipball/a06d10888a45afd97534506afb058ec38d9ba35b",
- "reference": "a06d10888a45afd97534506afb058ec38d9ba35b",
+ "url": "https://api.github.com/repos/symfony/debug/zipball/34f6ac18c2974ca5fce68adf419ee7d15def6f11",
+ "reference": "34f6ac18c2974ca5fce68adf419ee7d15def6f11",
"shasum": ""
},
"require": {
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.0-dev"
+ "dev-master": "3.1-dev"
}
},
"autoload": {
],
"description": "Symfony Debug Component",
"homepage": "https://symfony.com",
- "time": "2016-03-30 10:41:14"
+ "time": "2016-08-23 13:39:15"
},
{
"name": "symfony/event-dispatcher",
- "version": "v3.0.4",
+ "version": "v3.1.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
- "reference": "9002dcf018d884d294b1ef20a6f968efc1128f39"
+ "reference": "c0c00c80b3a69132c4e55c3e7db32b4a387615e5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9002dcf018d884d294b1ef20a6f968efc1128f39",
- "reference": "9002dcf018d884d294b1ef20a6f968efc1128f39",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/c0c00c80b3a69132c4e55c3e7db32b4a387615e5",
+ "reference": "c0c00c80b3a69132c4e55c3e7db32b4a387615e5",
"shasum": ""
},
"require": {
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.0-dev"
+ "dev-master": "3.1-dev"
}
},
"autoload": {
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
- "time": "2016-03-10 10:34:12"
+ "time": "2016-07-19 10:45:57"
},
{
"name": "symfony/finder",
- "version": "v3.0.4",
+ "version": "v3.1.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
- "reference": "c54e407b35bc098916704e9fd090da21da4c4f52"
+ "reference": "e568ef1784f447a0e54dcb6f6de30b9747b0f577"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/finder/zipball/c54e407b35bc098916704e9fd090da21da4c4f52",
- "reference": "c54e407b35bc098916704e9fd090da21da4c4f52",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/e568ef1784f447a0e54dcb6f6de30b9747b0f577",
+ "reference": "e568ef1784f447a0e54dcb6f6de30b9747b0f577",
"shasum": ""
},
"require": {
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.0-dev"
+ "dev-master": "3.1-dev"
}
},
"autoload": {
],
"description": "Symfony Finder Component",
"homepage": "https://symfony.com",
- "time": "2016-03-10 11:13:05"
+ "time": "2016-08-26 12:04:02"
},
{
"name": "symfony/http-foundation",
- "version": "v3.0.4",
+ "version": "v3.1.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
- "reference": "99f38445a874e7becb8afc4b4a79ee181cf6ec3f"
+ "reference": "63592e00fd90632b57ee50220a1ddb29b6bf3bb4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-foundation/zipball/99f38445a874e7becb8afc4b4a79ee181cf6ec3f",
- "reference": "99f38445a874e7becb8afc4b4a79ee181cf6ec3f",
+ "url": "https://api.github.com/repos/symfony/http-foundation/zipball/63592e00fd90632b57ee50220a1ddb29b6bf3bb4",
+ "reference": "63592e00fd90632b57ee50220a1ddb29b6bf3bb4",
"shasum": ""
},
"require": {
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.0-dev"
+ "dev-master": "3.1-dev"
}
},
"autoload": {
],
"description": "Symfony HttpFoundation Component",
"homepage": "https://symfony.com",
- "time": "2016-03-27 14:50:32"
+ "time": "2016-08-22 12:11:19"
},
{
"name": "symfony/http-kernel",
- "version": "v3.0.4",
+ "version": "v3.1.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
- "reference": "579f828489659d7b3430f4bd9b67b4618b387dea"
+ "reference": "aeda215d6b01f119508c090d2a09ebb5b0bc61f3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-kernel/zipball/579f828489659d7b3430f4bd9b67b4618b387dea",
- "reference": "579f828489659d7b3430f4bd9b67b4618b387dea",
+ "url": "https://api.github.com/repos/symfony/http-kernel/zipball/aeda215d6b01f119508c090d2a09ebb5b0bc61f3",
+ "reference": "aeda215d6b01f119508c090d2a09ebb5b0bc61f3",
"shasum": ""
},
"require": {
"psr/log": "~1.0",
"symfony/debug": "~2.8|~3.0",
"symfony/event-dispatcher": "~2.8|~3.0",
- "symfony/http-foundation": "~2.8|~3.0"
+ "symfony/http-foundation": "~2.8.8|~3.0.8|~3.1.2|~3.2"
},
"conflict": {
"symfony/config": "<2.8"
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.0-dev"
+ "dev-master": "3.1-dev"
}
},
"autoload": {
],
"description": "Symfony HttpKernel Component",
"homepage": "https://symfony.com",
- "time": "2016-03-25 01:41:20"
+ "time": "2016-09-03 15:28:24"
},
{
"name": "symfony/polyfill-mbstring",
- "version": "v1.1.1",
+ "version": "v1.2.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "1289d16209491b584839022f29257ad859b8532d"
+ "reference": "dff51f72b0706335131b00a7f49606168c582594"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/1289d16209491b584839022f29257ad859b8532d",
- "reference": "1289d16209491b584839022f29257ad859b8532d",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/dff51f72b0706335131b00a7f49606168c582594",
+ "reference": "dff51f72b0706335131b00a7f49606168c582594",
"shasum": ""
},
"require": {
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.1-dev"
+ "dev-master": "1.2-dev"
}
},
"autoload": {
"portable",
"shim"
],
- "time": "2016-01-20 09:13:37"
+ "time": "2016-05-18 14:26:46"
},
{
"name": "symfony/polyfill-php56",
- "version": "v1.1.1",
+ "version": "v1.2.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php56.git",
- "reference": "4d891fff050101a53a4caabb03277284942d1ad9"
+ "reference": "3edf57a8fbf9a927533344cef65ad7e1cf31030a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/4d891fff050101a53a4caabb03277284942d1ad9",
- "reference": "4d891fff050101a53a4caabb03277284942d1ad9",
+ "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/3edf57a8fbf9a927533344cef65ad7e1cf31030a",
+ "reference": "3edf57a8fbf9a927533344cef65ad7e1cf31030a",
"shasum": ""
},
"require": {
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.1-dev"
+ "dev-master": "1.2-dev"
}
},
"autoload": {
"portable",
"shim"
],
- "time": "2016-01-20 09:13:37"
+ "time": "2016-05-18 14:26:46"
},
{
"name": "symfony/polyfill-util",
- "version": "v1.1.1",
+ "version": "v1.2.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-util.git",
- "reference": "8de62801aa12bc4dfcf85eef5d21981ae7bb3cc4"
+ "reference": "ef830ce3d218e622b221d6bfad42c751d974bf99"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/8de62801aa12bc4dfcf85eef5d21981ae7bb3cc4",
- "reference": "8de62801aa12bc4dfcf85eef5d21981ae7bb3cc4",
+ "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/ef830ce3d218e622b221d6bfad42c751d974bf99",
+ "reference": "ef830ce3d218e622b221d6bfad42c751d974bf99",
"shasum": ""
},
"require": {
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.1-dev"
+ "dev-master": "1.2-dev"
}
},
"autoload": {
"polyfill",
"shim"
],
- "time": "2016-01-20 09:13:37"
+ "time": "2016-05-18 14:26:46"
},
{
"name": "symfony/process",
- "version": "v3.0.4",
+ "version": "v3.1.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
- "reference": "e6f1f98bbd355d209a992bfff45e7edfbd4a0776"
+ "reference": "e64e93041c80e77197ace5ab9385dedb5a143697"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/e6f1f98bbd355d209a992bfff45e7edfbd4a0776",
- "reference": "e6f1f98bbd355d209a992bfff45e7edfbd4a0776",
+ "url": "https://api.github.com/repos/symfony/process/zipball/e64e93041c80e77197ace5ab9385dedb5a143697",
+ "reference": "e64e93041c80e77197ace5ab9385dedb5a143697",
"shasum": ""
},
"require": {
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.0-dev"
+ "dev-master": "3.1-dev"
}
},
"autoload": {
],
"description": "Symfony Process Component",
"homepage": "https://symfony.com",
- "time": "2016-03-30 10:41:14"
+ "time": "2016-08-16 14:58:24"
},
{
"name": "symfony/routing",
- "version": "v3.0.4",
+ "version": "v3.1.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/routing.git",
- "reference": "d061b609f2d0769494c381ec92f5c5cc5e4a20aa"
+ "reference": "8edf62498a1a4c57ba317664a4b698339c10cdf6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/routing/zipball/d061b609f2d0769494c381ec92f5c5cc5e4a20aa",
- "reference": "d061b609f2d0769494c381ec92f5c5cc5e4a20aa",
+ "url": "https://api.github.com/repos/symfony/routing/zipball/8edf62498a1a4c57ba317664a4b698339c10cdf6",
+ "reference": "8edf62498a1a4c57ba317664a4b698339c10cdf6",
"shasum": ""
},
"require": {
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.0-dev"
+ "dev-master": "3.1-dev"
}
},
"autoload": {
"uri",
"url"
],
- "time": "2016-03-23 13:23:25"
+ "time": "2016-08-16 14:58:24"
},
{
"name": "symfony/translation",
- "version": "v3.0.4",
+ "version": "v3.1.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
- "reference": "f7a07af51ea067745a521dab1e3152044a2fb1f2"
+ "reference": "a35edc277513c9bc0f063ca174c36b346f974528"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/translation/zipball/f7a07af51ea067745a521dab1e3152044a2fb1f2",
- "reference": "f7a07af51ea067745a521dab1e3152044a2fb1f2",
+ "url": "https://api.github.com/repos/symfony/translation/zipball/a35edc277513c9bc0f063ca174c36b346f974528",
+ "reference": "a35edc277513c9bc0f063ca174c36b346f974528",
"shasum": ""
},
"require": {
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.0-dev"
+ "dev-master": "3.1-dev"
}
},
"autoload": {
],
"description": "Symfony Translation Component",
"homepage": "https://symfony.com",
- "time": "2016-03-25 01:41:20"
+ "time": "2016-08-05 08:37:39"
},
{
"name": "symfony/var-dumper",
- "version": "v3.0.4",
+ "version": "v3.1.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
- "reference": "3841ed86527d18ee2c35fe4afb1b2fc60f8fae79"
+ "reference": "62ee73706c421654a4c840028954510277f7dfc8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-dumper/zipball/3841ed86527d18ee2c35fe4afb1b2fc60f8fae79",
- "reference": "3841ed86527d18ee2c35fe4afb1b2fc60f8fae79",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/62ee73706c421654a4c840028954510277f7dfc8",
+ "reference": "62ee73706c421654a4c840028954510277f7dfc8",
"shasum": ""
},
"require": {
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.0-dev"
+ "dev-master": "3.1-dev"
}
},
"autoload": {
"debug",
"dump"
],
- "time": "2016-03-10 10:34:12"
+ "time": "2016-08-31 09:05:42"
},
{
"name": "vlucas/phpdotenv",
- "version": "v2.2.0",
+ "version": "v2.4.0",
"source": {
"type": "git",
"url": "https://github.com/vlucas/phpdotenv.git",
- "reference": "9caf304153dc2288e4970caec6f1f3b3bc205412"
+ "reference": "3cc116adbe4b11be5ec557bf1d24dc5e3a21d18c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/9caf304153dc2288e4970caec6f1f3b3bc205412",
- "reference": "9caf304153dc2288e4970caec6f1f3b3bc205412",
+ "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/3cc116adbe4b11be5ec557bf1d24dc5e3a21d18c",
+ "reference": "3cc116adbe4b11be5ec557bf1d24dc5e3a21d18c",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
"require-dev": {
- "phpunit/phpunit": "^4.8|^5.0"
+ "phpunit/phpunit": "^4.8 || ^5.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.2-dev"
+ "dev-master": "2.4-dev"
}
},
"autoload": {
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "BSD"
+ "BSD-3-Clause-Attribution"
],
"authors": [
{
}
],
"description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.",
- "homepage": "http://github.com/vlucas/phpdotenv",
"keywords": [
"dotenv",
"env",
"environment"
],
- "time": "2015-12-29 15:10:30"
+ "time": "2016-09-01 10:05:43"
}
],
"packages-dev": [
},
{
"name": "fzaninotto/faker",
- "version": "v1.5.0",
+ "version": "v1.6.0",
"source": {
"type": "git",
"url": "https://github.com/fzaninotto/Faker.git",
- "reference": "d0190b156bcca848d401fb80f31f504f37141c8d"
+ "reference": "44f9a286a04b80c76a4e5fb7aad8bb539b920123"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/d0190b156bcca848d401fb80f31f504f37141c8d",
- "reference": "d0190b156bcca848d401fb80f31f504f37141c8d",
+ "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/44f9a286a04b80c76a4e5fb7aad8bb539b920123",
+ "reference": "44f9a286a04b80c76a4e5fb7aad8bb539b920123",
"shasum": ""
},
"require": {
- "php": ">=5.3.3"
+ "php": "^5.3.3|^7.0"
},
"require-dev": {
+ "ext-intl": "*",
"phpunit/phpunit": "~4.0",
"squizlabs/php_codesniffer": "~1.5"
},
- "suggest": {
- "ext-intl": "*"
- },
"type": "library",
"extra": {
- "branch-alias": {
- "dev-master": "1.5.x-dev"
- }
+ "branch-alias": []
},
"autoload": {
"psr-4": {
"faker",
"fixtures"
],
- "time": "2015-05-29 06:29:14"
+ "time": "2016-04-29 12:21:54"
},
{
"name": "hamcrest/hamcrest-php",
},
{
"name": "mockery/mockery",
- "version": "0.9.4",
+ "version": "0.9.5",
"source": {
"type": "git",
"url": "https://github.com/padraic/mockery.git",
- "reference": "70bba85e4aabc9449626651f48b9018ede04f86b"
+ "reference": "4db079511a283e5aba1b3c2fb19037c645e70fc2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/padraic/mockery/zipball/70bba85e4aabc9449626651f48b9018ede04f86b",
- "reference": "70bba85e4aabc9449626651f48b9018ede04f86b",
+ "url": "https://api.github.com/repos/padraic/mockery/zipball/4db079511a283e5aba1b3c2fb19037c645e70fc2",
+ "reference": "4db079511a283e5aba1b3c2fb19037c645e70fc2",
"shasum": ""
},
"require": {
"test double",
"testing"
],
- "time": "2015-04-02 19:54:00"
+ "time": "2016-05-22 21:52:33"
},
{
- "name": "phpspec/php-diff",
- "version": "v1.0.2",
+ "name": "myclabs/deep-copy",
+ "version": "1.5.4",
"source": {
"type": "git",
- "url": "https://github.com/phpspec/php-diff.git",
- "reference": "30e103d19519fe678ae64a60d77884ef3d71b28a"
+ "url": "https://github.com/myclabs/DeepCopy.git",
+ "reference": "ea74994a3dc7f8d2f65a06009348f2d63c81e61f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpspec/php-diff/zipball/30e103d19519fe678ae64a60d77884ef3d71b28a",
- "reference": "30e103d19519fe678ae64a60d77884ef3d71b28a",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/ea74994a3dc7f8d2f65a06009348f2d63c81e61f",
+ "reference": "ea74994a3dc7f8d2f65a06009348f2d63c81e61f",
"shasum": ""
},
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "doctrine/collections": "1.*",
+ "phpunit/phpunit": "~4.1"
+ },
"type": "library",
"autoload": {
- "psr-0": {
- "Diff": "lib/"
+ "psr-4": {
+ "DeepCopy\\": "src/DeepCopy/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "BSD-3-Clause"
+ "MIT"
+ ],
+ "description": "Create deep copies (clones) of your objects",
+ "homepage": "https://github.com/myclabs/DeepCopy",
+ "keywords": [
+ "clone",
+ "copy",
+ "duplicate",
+ "object",
+ "object graph"
+ ],
+ "time": "2016-09-16 13:37:59"
+ },
+ {
+ "name": "phpdocumentor/reflection-common",
+ "version": "1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
+ "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c",
+ "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.6"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": [
+ "src"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
],
"authors": [
{
- "name": "Chris Boulton",
- "homepage": "http://github.com/chrisboulton",
- "role": "Original developer"
+ "name": "Jaap van Otterdijk",
}
],
- "description": "A comprehensive library for generating differences between two hashable objects (strings or arrays).",
- "time": "2013-11-01 13:02:21"
+ "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
+ "homepage": "http://www.phpdoc.org",
+ "keywords": [
+ "FQSEN",
+ "phpDocumentor",
+ "phpdoc",
+ "reflection",
+ "static analysis"
+ ],
+ "time": "2015-12-27 11:43:31"
},
{
- "name": "phpspec/phpspec",
- "version": "2.5.0",
+ "name": "phpdocumentor/reflection-docblock",
+ "version": "3.1.0",
"source": {
"type": "git",
- "url": "https://github.com/phpspec/phpspec.git",
- "reference": "385ecb015e97c13818074f1517928b24d4a26067"
+ "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
+ "reference": "9270140b940ff02e58ec577c237274e92cd40cdd"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpspec/phpspec/zipball/385ecb015e97c13818074f1517928b24d4a26067",
- "reference": "385ecb015e97c13818074f1517928b24d4a26067",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9270140b940ff02e58ec577c237274e92cd40cdd",
+ "reference": "9270140b940ff02e58ec577c237274e92cd40cdd",
"shasum": ""
},
"require": {
- "doctrine/instantiator": "^1.0.1",
- "ext-tokenizer": "*",
- "php": ">=5.3.3",
- "phpspec/php-diff": "~1.0.0",
- "phpspec/prophecy": "~1.4",
- "sebastian/exporter": "~1.0",
- "symfony/console": "~2.3|~3.0",
- "symfony/event-dispatcher": "~2.1|~3.0",
- "symfony/finder": "~2.1|~3.0",
- "symfony/process": "^2.6|~3.0",
- "symfony/yaml": "~2.1|~3.0"
+ "php": ">=5.5",
+ "phpdocumentor/reflection-common": "^1.0@dev",
+ "phpdocumentor/type-resolver": "^0.2.0",
+ "webmozart/assert": "^1.0"
},
"require-dev": {
- "behat/behat": "^3.0.11",
- "bossa/phpspec2-expect": "~1.0",
- "phpunit/phpunit": "~4.4",
- "symfony/filesystem": "~2.1|~3.0"
+ "mockery/mockery": "^0.9.4",
+ "phpunit/phpunit": "^4.4"
},
- "suggest": {
- "phpspec/nyan-formatters": "~1.0 – Adds Nyan formatters"
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": [
+ "src/"
+ ]
+ }
},
- "bin": [
- "bin/phpspec"
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
],
+ "authors": [
+ {
+ "name": "Mike van Riel",
+ }
+ ],
+ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
+ "time": "2016-06-10 09:48:41"
+ },
+ {
+ "name": "phpdocumentor/type-resolver",
+ "version": "0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/TypeResolver.git",
+ "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/b39c7a5b194f9ed7bd0dd345c751007a41862443",
+ "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5",
+ "phpdocumentor/reflection-common": "^1.0"
+ },
+ "require-dev": {
+ "mockery/mockery": "^0.9.4",
+ "phpunit/phpunit": "^5.2||^4.8.24"
+ },
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.2.x-dev"
+ "dev-master": "1.0.x-dev"
}
},
"autoload": {
- "psr-0": {
- "PhpSpec": "src/"
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": [
+ "src/"
+ ]
}
},
"notification-url": "https://packagist.org/downloads/",
],
"authors": [
{
- "name": "Konstantin Kudryashov",
- "homepage": "http://everzet.com"
- },
- {
- "name": "Marcello Duarte",
- "homepage": "http://marcelloduarte.net/"
+ "name": "Mike van Riel",
}
],
- "description": "Specification-oriented BDD framework for PHP 5.3+",
- "homepage": "http://phpspec.net/",
- "keywords": [
- "BDD",
- "SpecBDD",
- "TDD",
- "spec",
- "specification",
- "testing",
- "tests"
- ],
- "time": "2016-03-20 20:34:32"
+ "time": "2016-06-10 07:14:17"
},
{
"name": "phpspec/prophecy",
- "version": "v1.6.0",
+ "version": "v1.6.1",
"source": {
"type": "git",
"url": "https://github.com/phpspec/prophecy.git",
- "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972"
+ "reference": "58a8137754bc24b25740d4281399a4a3596058e0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpspec/prophecy/zipball/3c91bdf81797d725b14cb62906f9a4ce44235972",
- "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972",
+ "url": "https://api.github.com/repos/phpspec/prophecy/zipball/58a8137754bc24b25740d4281399a4a3596058e0",
+ "reference": "58a8137754bc24b25740d4281399a4a3596058e0",
"shasum": ""
},
"require": {
"doctrine/instantiator": "^1.0.2",
"php": "^5.3|^7.0",
- "phpdocumentor/reflection-docblock": "~2.0",
- "sebastian/comparator": "~1.1",
- "sebastian/recursion-context": "~1.0"
+ "phpdocumentor/reflection-docblock": "^2.0|^3.0.2",
+ "sebastian/comparator": "^1.1",
+ "sebastian/recursion-context": "^1.0"
},
"require-dev": {
- "phpspec/phpspec": "~2.0"
+ "phpspec/phpspec": "^2.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.5.x-dev"
+ "dev-master": "1.6.x-dev"
}
},
"autoload": {
"spy",
"stub"
],
- "time": "2016-02-15 07:46:21"
+ "time": "2016-06-07 08:13:47"
},
{
"name": "phpunit/php-code-coverage",
- "version": "2.2.4",
+ "version": "4.0.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
- "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979"
+ "reference": "5f3f7e736d6319d5f1fc402aff8b026da26709a3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979",
- "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/5f3f7e736d6319d5f1fc402aff8b026da26709a3",
+ "reference": "5f3f7e736d6319d5f1fc402aff8b026da26709a3",
"shasum": ""
},
"require": {
- "php": ">=5.3.3",
+ "php": "^5.6 || ^7.0",
"phpunit/php-file-iterator": "~1.3",
"phpunit/php-text-template": "~1.2",
- "phpunit/php-token-stream": "~1.3",
- "sebastian/environment": "^1.3.2",
- "sebastian/version": "~1.0"
+ "phpunit/php-token-stream": "^1.4.2",
+ "sebastian/code-unit-reverse-lookup": "~1.0",
+ "sebastian/environment": "^1.3.2 || ^2.0",
+ "sebastian/version": "~1.0|~2.0"
},
"require-dev": {
"ext-xdebug": ">=2.1.4",
- "phpunit/phpunit": "~4"
+ "phpunit/phpunit": "^5.4"
},
"suggest": {
"ext-dom": "*",
- "ext-xdebug": ">=2.2.1",
+ "ext-xdebug": ">=2.4.0",
"ext-xmlwriter": "*"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.2.x-dev"
+ "dev-master": "4.0.x-dev"
}
},
"autoload": {
"testing",
"xunit"
],
- "time": "2015-10-06 15:47:00"
+ "time": "2016-07-26 14:39:29"
},
{
"name": "phpunit/php-file-iterator",
},
{
"name": "phpunit/php-timer",
- "version": "1.0.7",
+ "version": "1.0.8",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-timer.git",
- "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b"
+ "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3e82f4e9fc92665fafd9157568e4dcb01d014e5b",
- "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/38e9124049cf1a164f1e4537caf19c99bf1eb260",
+ "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
+ "require-dev": {
+ "phpunit/phpunit": "~4|~5"
+ },
"type": "library",
"autoload": {
"classmap": [
"keywords": [
"timer"
],
- "time": "2015-06-21 08:01:12"
+ "time": "2016-05-12 18:03:57"
},
{
"name": "phpunit/php-token-stream",
},
{
"name": "phpunit/phpunit",
- "version": "4.8.24",
+ "version": "5.5.5",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "a1066c562c52900a142a0e2bbf0582994671385e"
+ "reference": "a57126dc681b08289fef6ac96a48e30656f84350"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a1066c562c52900a142a0e2bbf0582994671385e",
- "reference": "a1066c562c52900a142a0e2bbf0582994671385e",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a57126dc681b08289fef6ac96a48e30656f84350",
+ "reference": "a57126dc681b08289fef6ac96a48e30656f84350",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-json": "*",
- "ext-pcre": "*",
- "ext-reflection": "*",
- "ext-spl": "*",
- "php": ">=5.3.3",
+ "ext-libxml": "*",
+ "ext-mbstring": "*",
+ "ext-xml": "*",
+ "myclabs/deep-copy": "~1.3",
+ "php": "^5.6 || ^7.0",
"phpspec/prophecy": "^1.3.1",
- "phpunit/php-code-coverage": "~2.1",
+ "phpunit/php-code-coverage": "^4.0.1",
"phpunit/php-file-iterator": "~1.4",
"phpunit/php-text-template": "~1.2",
- "phpunit/php-timer": ">=1.0.6",
- "phpunit/phpunit-mock-objects": "~2.3",
+ "phpunit/php-timer": "^1.0.6",
+ "phpunit/phpunit-mock-objects": "^3.2",
"sebastian/comparator": "~1.1",
"sebastian/diff": "~1.2",
- "sebastian/environment": "~1.3",
+ "sebastian/environment": "^1.3 || ^2.0",
"sebastian/exporter": "~1.2",
"sebastian/global-state": "~1.0",
- "sebastian/version": "~1.0",
+ "sebastian/object-enumerator": "~1.0",
+ "sebastian/resource-operations": "~1.0",
+ "sebastian/version": "~1.0|~2.0",
"symfony/yaml": "~2.1|~3.0"
},
+ "conflict": {
+ "phpdocumentor/reflection-docblock": "3.0.2"
+ },
+ "require-dev": {
+ "ext-pdo": "*"
+ },
"suggest": {
+ "ext-tidy": "*",
+ "ext-xdebug": "*",
"phpunit/php-invoker": "~1.1"
},
"bin": [
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "4.8.x-dev"
+ "dev-master": "5.5.x-dev"
}
},
"autoload": {
"testing",
"xunit"
],
- "time": "2016-03-14 06:16:08"
+ "time": "2016-09-21 14:40:13"
},
{
"name": "phpunit/phpunit-mock-objects",
- "version": "2.3.8",
+ "version": "3.2.7",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
- "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983"
+ "reference": "546898a2c0c356ef2891b39dd7d07f5d82c8ed0a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983",
- "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/546898a2c0c356ef2891b39dd7d07f5d82c8ed0a",
+ "reference": "546898a2c0c356ef2891b39dd7d07f5d82c8ed0a",
"shasum": ""
},
"require": {
"doctrine/instantiator": "^1.0.2",
- "php": ">=5.3.3",
- "phpunit/php-text-template": "~1.2",
- "sebastian/exporter": "~1.2"
+ "php": "^5.6 || ^7.0",
+ "phpunit/php-text-template": "^1.2",
+ "sebastian/exporter": "^1.2"
+ },
+ "conflict": {
+ "phpunit/phpunit": "<5.4.0"
},
"require-dev": {
- "phpunit/phpunit": "~4.4"
+ "phpunit/phpunit": "^5.4"
},
"suggest": {
"ext-soap": "*"
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.3.x-dev"
+ "dev-master": "3.2.x-dev"
}
},
"autoload": {
"mock",
"xunit"
],
- "time": "2015-10-02 06:51:40"
+ "time": "2016-09-06 16:07:45"
+ },
+ {
+ "name": "sebastian/code-unit-reverse-lookup",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
+ "reference": "c36f5e7cfce482fde5bf8d10d41a53591e0198fe"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/c36f5e7cfce482fde5bf8d10d41a53591e0198fe",
+ "reference": "c36f5e7cfce482fde5bf8d10d41a53591e0198fe",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ }
+ ],
+ "description": "Looks up which function or method a line of code belongs to",
+ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
+ "time": "2016-02-13 06:45:14"
},
{
"name": "sebastian/comparator",
},
{
"name": "sebastian/environment",
- "version": "1.3.5",
+ "version": "1.3.8",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/environment.git",
- "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf"
+ "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf",
- "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea",
+ "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea",
"shasum": ""
},
"require": {
- "php": ">=5.3.3"
+ "php": "^5.3.3 || ^7.0"
},
"require-dev": {
- "phpunit/phpunit": "~4.4"
+ "phpunit/phpunit": "^4.8 || ^5.0"
},
"type": "library",
"extra": {
"environment",
"hhvm"
],
- "time": "2016-02-26 18:40:46"
+ "time": "2016-08-18 05:49:44"
},
{
"name": "sebastian/exporter",
- "version": "1.2.1",
+ "version": "1.2.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/exporter.git",
- "reference": "7ae5513327cb536431847bcc0c10edba2701064e"
+ "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/7ae5513327cb536431847bcc0c10edba2701064e",
- "reference": "7ae5513327cb536431847bcc0c10edba2701064e",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4",
+ "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4",
"shasum": ""
},
"require": {
"sebastian/recursion-context": "~1.0"
},
"require-dev": {
+ "ext-mbstring": "*",
"phpunit/phpunit": "~4.4"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.2.x-dev"
+ "dev-master": "1.3.x-dev"
}
},
"autoload": {
"export",
"exporter"
],
- "time": "2015-06-21 07:55:53"
+ "time": "2016-06-17 09:04:28"
},
{
"name": "sebastian/global-state",
],
"time": "2015-10-12 03:26:01"
},
+ {
+ "name": "sebastian/object-enumerator",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-enumerator.git",
+ "reference": "d4ca2fb70344987502567bc50081c03e6192fb26"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/d4ca2fb70344987502567bc50081c03e6192fb26",
+ "reference": "d4ca2fb70344987502567bc50081c03e6192fb26",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6",
+ "sebastian/recursion-context": "~1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ }
+ ],
+ "description": "Traverses array structures and object graphs to enumerate all referenced objects",
+ "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
+ "time": "2016-01-28 13:25:10"
+ },
{
"name": "sebastian/recursion-context",
"version": "1.0.2",
"homepage": "http://www.github.com/sebastianbergmann/recursion-context",
"time": "2015-11-11 19:50:13"
},
+ {
+ "name": "sebastian/resource-operations",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/resource-operations.git",
+ "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52",
+ "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ }
+ ],
+ "description": "Provides a list of PHP built-in functions that operate on resources",
+ "homepage": "https://www.github.com/sebastianbergmann/resource-operations",
+ "time": "2015-07-28 20:34:47"
+ },
{
"name": "sebastian/version",
- "version": "1.0.6",
+ "version": "2.0.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/version.git",
- "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6"
+ "reference": "c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
- "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
+ "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5",
+ "reference": "c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5",
"shasum": ""
},
+ "require": {
+ "php": ">=5.6"
+ },
"type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
"autoload": {
"classmap": [
"src/"
],
"description": "Library that helps with managing the version number of Git-hosted PHP projects",
"homepage": "https://github.com/sebastianbergmann/version",
- "time": "2015-06-21 13:59:46"
+ "time": "2016-02-04 12:56:52"
},
{
"name": "symfony/css-selector",
- "version": "v3.0.4",
+ "version": "v3.1.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
- "reference": "65e764f404685f2dc20c057e889b3ad04b2e2db0"
+ "reference": "2851e1932d77ce727776154d659b232d061e816a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/css-selector/zipball/65e764f404685f2dc20c057e889b3ad04b2e2db0",
- "reference": "65e764f404685f2dc20c057e889b3ad04b2e2db0",
+ "url": "https://api.github.com/repos/symfony/css-selector/zipball/2851e1932d77ce727776154d659b232d061e816a",
+ "reference": "2851e1932d77ce727776154d659b232d061e816a",
"shasum": ""
},
"require": {
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.0-dev"
+ "dev-master": "3.1-dev"
}
},
"autoload": {
],
"description": "Symfony CssSelector Component",
"homepage": "https://symfony.com",
- "time": "2016-03-04 07:55:57"
+ "time": "2016-06-29 05:41:56"
},
{
"name": "symfony/dom-crawler",
- "version": "v3.0.4",
+ "version": "v3.1.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/dom-crawler.git",
- "reference": "18a06d7a9af41718c20764a674a0ebba3bc40d1f"
+ "reference": "bb7395e8b1db3654de82b9f35d019958276de4d7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/18a06d7a9af41718c20764a674a0ebba3bc40d1f",
- "reference": "18a06d7a9af41718c20764a674a0ebba3bc40d1f",
+ "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/bb7395e8b1db3654de82b9f35d019958276de4d7",
+ "reference": "bb7395e8b1db3654de82b9f35d019958276de4d7",
"shasum": ""
},
"require": {
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.0-dev"
+ "dev-master": "3.1-dev"
}
},
"autoload": {
],
"description": "Symfony DomCrawler Component",
"homepage": "https://symfony.com",
- "time": "2016-03-23 13:23:25"
+ "time": "2016-08-05 08:37:39"
},
{
"name": "symfony/yaml",
- "version": "v3.0.4",
+ "version": "v3.1.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
- "reference": "0047c8366744a16de7516622c5b7355336afae96"
+ "reference": "f291ed25eb1435bddbe8a96caaef16469c2a092d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/0047c8366744a16de7516622c5b7355336afae96",
- "reference": "0047c8366744a16de7516622c5b7355336afae96",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/f291ed25eb1435bddbe8a96caaef16469c2a092d",
+ "reference": "f291ed25eb1435bddbe8a96caaef16469c2a092d",
"shasum": ""
},
"require": {
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.0-dev"
+ "dev-master": "3.1-dev"
}
},
"autoload": {
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
- "time": "2016-03-04 07:55:57"
+ "time": "2016-09-02 02:12:52"
+ },
+ {
+ "name": "webmozart/assert",
+ "version": "1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/webmozart/assert.git",
+ "reference": "bb2d123231c095735130cc8f6d31385a44c7b308"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/webmozart/assert/zipball/bb2d123231c095735130cc8f6d31385a44c7b308",
+ "reference": "bb2d123231c095735130cc8f6d31385a44c7b308",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3.3|^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.6",
+ "sebastian/version": "^1.0.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.2-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Webmozart\\Assert\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Bernhard Schussek",
+ }
+ ],
+ "description": "Assertions to validate method input/output with nice error messages.",
+ "keywords": [
+ "assert",
+ "check",
+ "validate"
+ ],
+ "time": "2016-08-09 15:02:57"
}
],
"aliases": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
- "php": ">=5.5.9"
+ "php": ">=5.6.4",
+ "ext-tidy": "*"
},
"platform-dev": []
}
Illuminate\Translation\TranslationServiceProvider::class,
Illuminate\Validation\ValidationServiceProvider::class,
Illuminate\View\ViewServiceProvider::class,
+ Illuminate\Notifications\NotificationServiceProvider::class,
Laravel\Socialite\SocialiteServiceProvider::class,
/**
BookStack\Providers\AuthServiceProvider::class,
BookStack\Providers\AppServiceProvider::class,
+ BookStack\Providers\BroadcastServiceProvider::class,
BookStack\Providers\EventServiceProvider::class,
BookStack\Providers\RouteServiceProvider::class,
BookStack\Providers\CustomFacadeProvider::class,
'Lang' => Illuminate\Support\Facades\Lang::class,
'Log' => Illuminate\Support\Facades\Log::class,
'Mail' => Illuminate\Support\Facades\Mail::class,
+ 'Notification' => Illuminate\Support\Facades\Notification::class,
'Password' => Illuminate\Support\Facades\Password::class,
'Queue' => Illuminate\Support\Facades\Queue::class,
'Redirect' => Illuminate\Support\Facades\Redirect::class,
'local' => [
'driver' => 'local',
- 'root' => public_path(),
+ 'root' => base_path(),
],
'ftp' => [
return [
'app-name' => 'BookStack',
+ 'app-name-header' => true,
'app-editor' => 'wysiwyg',
'app-color' => '#0288D1',
'app-color-light' => 'rgba(21, 101, 192, 0.15)',
// Set all current users as admins
// (At this point only the initially create user should be an admin)
- $users = DB::table('users')->get();
+ $users = DB::table('users')->get()->all();
foreach ($users as $user) {
DB::table('role_user')->insert([
'role_id' => $adminId,
--- /dev/null
+<?php
+
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Migrations\Migration;
+
+class RemoveHiddenRoles extends Migration
+{
+ /**
+ * Run the migrations.
+ *
+ * @return void
+ */
+ public function up()
+ {
+ // Remove the hidden property from roles
+ Schema::table('roles', function(Blueprint $table) {
+ $table->dropColumn('hidden');
+ });
+
+ // Add column to mark system users
+ Schema::table('users', function(Blueprint $table) {
+ $table->string('system_name')->nullable()->index();
+ });
+
+ // Insert our new public system user.
+ $publicUserId = DB::table('users')->insertGetId([
+ 'name' => 'Guest',
+ 'system_name' => 'public',
+ 'email_confirmed' => true,
+ 'created_at' => \Carbon\Carbon::now(),
+ 'updated_at' => \Carbon\Carbon::now(),
+ ]);
+
+ // Get the public role
+ $publicRole = DB::table('roles')->where('system_name', '=', 'public')->first();
+
+ // Connect the new public user to the public role
+ DB::table('role_user')->insert([
+ 'user_id' => $publicUserId,
+ 'role_id' => $publicRole->id
+ ]);
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('roles', function(Blueprint $table) {
+ $table->boolean('hidden')->default(false);
+ $table->index('hidden');
+ });
+
+ DB::table('users')->where('system_name', '=', 'public')->delete();
+
+ Schema::table('users', function(Blueprint $table) {
+ $table->dropColumn('system_name');
+ });
+
+ DB::table('roles')->where('system_name', '=', 'public')->update(['hidden' => true]);
+ }
+}
--- /dev/null
+<?php
+
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Migrations\Migration;
+
+class CreateFilesTable extends Migration
+{
+ /**
+ * Run the migrations.
+ *
+ * @return void
+ */
+ public function up()
+ {
+ Schema::create('files', function (Blueprint $table) {
+ $table->increments('id');
+ $table->string('name');
+ $table->string('path');
+ $table->string('extension', 20);
+ $table->integer('uploaded_to');
+
+ $table->boolean('external');
+ $table->integer('order');
+
+ $table->integer('created_by');
+ $table->integer('updated_by');
+
+ $table->index('uploaded_to');
+ $table->timestamps();
+ });
+
+ // Get roles with permissions we need to change
+ $adminRoleId = DB::table('roles')->where('system_name', '=', 'admin')->first()->id;
+
+ // Create & attach new entity permissions
+ $ops = ['Create All', 'Create Own', 'Update All', 'Update Own', 'Delete All', 'Delete Own'];
+ $entity = 'File';
+ foreach ($ops as $op) {
+ $permissionId = DB::table('role_permissions')->insertGetId([
+ 'name' => strtolower($entity) . '-' . strtolower(str_replace(' ', '-', $op)),
+ 'display_name' => $op . ' ' . $entity . 's',
+ 'created_at' => \Carbon\Carbon::now()->toDateTimeString(),
+ 'updated_at' => \Carbon\Carbon::now()->toDateTimeString()
+ ]);
+ DB::table('permission_role')->insert([
+ 'role_id' => $adminRoleId,
+ 'permission_id' => $permissionId
+ ]);
+ }
+
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('files');
+
+ // Create & attach new entity permissions
+ $ops = ['Create All', 'Create Own', 'Update All', 'Update Own', 'Delete All', 'Delete Own'];
+ $entity = 'File';
+ foreach ($ops as $op) {
+ $permName = strtolower($entity) . '-' . strtolower(str_replace(' ', '-', $op));
+ DB::table('role_permissions')->where('name', '=', $permName)->delete();
+ }
+ }
+}
var elixir = require('laravel-elixir');
-// Custom extensions
-var gulp = require('gulp');
-var Task = elixir.Task;
-var fs = require('fs');
-
-elixir.extend('queryVersion', function(inputFiles) {
- new Task('queryVersion', function() {
- var manifestObject = {};
- var uidString = Date.now().toString(16).slice(4);
- for (var i = 0; i < inputFiles.length; i++) {
- var file = inputFiles[i];
- manifestObject[file] = file + '?version=' + uidString;
- }
- var fileContents = JSON.stringify(manifestObject, null, 1);
- fs.writeFileSync('public/build/manifest.json', fileContents);
- }).watch(['./public/css/*.css', './public/js/*.js']);
-});
-
-elixir(function(mix) {
- mix.sass('styles.scss')
- .sass('print-styles.scss')
- .sass('export-styles.scss')
- .browserify('global.js', 'public/js/common.js')
- .queryVersion(['css/styles.css', 'css/print-styles.css', 'js/common.js']);
+elixir(mix => {
+ mix.sass('styles.scss');
+ mix.sass('print-styles.scss');
+ mix.sass('export-styles.scss');
+ mix.browserify('global.js', './public/js/common.js');
});
{
"private": true,
- "devDependencies": {
- "gulp": "^3.9.0"
+ "scripts": {
+ "prod": "gulp --production",
+ "dev": "gulp watch"
},
- "dependencies": {
+ "devDependencies": {
"angular": "^1.5.5",
"angular-animate": "^1.5.5",
"angular-resource": "^1.5.5",
"angular-sanitize": "^1.5.5",
- "angular-ui-sortable": "^0.14.0",
- "babel-runtime": "^5.8.29",
- "bootstrap-sass": "^3.0.0",
+ "angular-ui-sortable": "^0.15.0",
"dropzone": "^4.0.1",
- "laravel-elixir": "^5.0.0",
+ "gulp": "^3.9.0",
+ "laravel-elixir": "^6.0.0-11",
+ "laravel-elixir-browserify-official": "^0.1.3",
"marked": "^0.3.5",
"moment": "^2.12.0",
"zeroclipboard": "^2.2.0"
+++ /dev/null
-suites:
- main:
- namespace: BookStack
- psr4_prefix: BookStack
- src_path: app
\ No newline at end of file
<env name="AUTH_METHOD" value="standard"/>
<env name="DISABLE_EXTERNAL_SERVICES" value="true"/>
<env name="LDAP_VERSION" value="3"/>
+ <env name="STORAGE_TYPE" value="local"/>
<env name="GITHUB_APP_ID" value="aaaaaaaaaaaaaa"/>
<env name="GITHUB_APP_SECRET" value="aaaaaaaaaaaaaa"/>
<env name="GOOGLE_APP_ID" value="aaaaaaaaaaaaaa"/>
[](https://github.com/ssddanbrown/BookStack/releases/latest)
[](https://github.com/ssddanbrown/BookStack/blob/master/LICENSE)
-[](https://travis-ci.org/ssddanbrown/BookStack)
+[](https://travis-ci.org/BookStackApp/BookStack)
A platform for storing and organising information and documentation. General information and documentation for BookStack can be found at https://www.bookstackapp.com/.
* [Installation Instructions](https://www.bookstackapp.com/docs/admin/installation)
* [Documentation](https://www.bookstackapp.com/docs)
-* [Demo Instance](https://demo.bookstackapp.com) *(Login username: `
[email protected]`. Password: `password`)*
+* [Demo Instance](https://demo.bookstackapp.com)
+ * *Password: `password`*
* [BookStack Blog](https://www.bookstackapp.com/blog)
## Development & Testing
php artisan db:seed --class=DummyContentSeeder --database=mysql_testing
```
-Once done you can run `phpunit` (or `./vendor/bin/phpunit` if `phpunit` is not found) in the application root directory to run all tests.
+Once done you can run `phpunit` in the application root directory to run all tests.
## License
* [TinyColorPicker](http://www.dematte.at/tinyColorPicker/index.html)
* [Marked](https://github.com/chjj/marked)
* [Moment.js](http://momentjs.com/)
+
+Additionally, Thank you [BrowserStack](https://www.browserstack.com/) for supporting us and making cross-browser testing easy.
+++ /dev/null
-<div class="dropzone-container">
- <div class="dz-message">Drop files or click here to upload</div>
-</div>
\ No newline at end of file
+++ /dev/null
-
-<div class="image-picker">
- <div>
- <img ng-if="image && image !== 'none'" ng-src="{{image}}" ng-class="{{imageClass}}" alt="Image Preview">
- <img ng-if="image === '' && defaultImage" ng-src="{{defaultImage}}" ng-class="{{imageClass}}" alt="Image Preview">
- </div>
- <button class="button" type="button" ng-click="showImageManager()">Select Image</button>
- <br>
-
- <button class="text-button" ng-click="reset()" type="button">Reset</button>
- <span ng-show="showRemove" class="sep">|</span>
- <button ng-show="showRemove" class="text-button neg" ng-click="remove()" type="button">Remove</button>
-
- <input type="hidden" ng-attr-name="{{name}}" ng-attr-id="{{name}}" ng-attr-value="{{value}}">
-</div>
\ No newline at end of file
+++ /dev/null
-<div class="toggle-switch" ng-click="switch()" ng-class="{'active': isActive}">
- <input type="hidden" ng-attr-name="{{name}}" ng-attr-value="{{value}}"/>
- <div class="switch-handle"></div>
-</div>
\ No newline at end of file
"use strict";
-const moment = require('moment');
+import moment from 'moment';
+import 'moment/locale/en-gb';
+moment.locale('en-gb');
module.exports = function (ngApp, events) {
$scope.imageDeleteSuccess = false;
$scope.uploadedTo = $attrs.uploadedTo;
$scope.view = 'all';
-
+
$scope.searching = false;
$scope.searchTerm = '';
$scope.hasMore = preSearchHasMore;
}
$scope.cancelSearch = cancelSearch;
-
+
/**
* Runs on image upload, Adds an image to local list of images
var isEdit = pageId !== 0;
var autosaveFrequency = 30; // AutoSave interval in seconds.
var isMarkdown = $attrs.editorType === 'markdown';
+ $scope.draftsEnabled = $attrs.draftsEnabled === 'true';
$scope.isUpdateDraft = Number($attrs.pageUpdateDraft) === 1;
$scope.isNewPageDraft = Number($attrs.pageNewDraft) === 1;
html: false
};
- if (isEdit) {
+ if (isEdit && $scope.draftsEnabled) {
setTimeout(() => {
startAutoSave();
}, 1000);
$scope.editorChange = function() {};
}
+ let lastSave = 0;
+
/**
* Start the AutoSave loop, Checks for content change
* before performing the costly AJAX request.
currentContent.html = $scope.editContent;
autoSave = $interval(() => {
+ // Return if manually saved recently to prevent bombarding the server
+ if (Date.now() - lastSave < (1000*autosaveFrequency)/2) return;
var newTitle = $('#name').val();
var newHtml = $scope.editContent;
}, 1000 * autosaveFrequency);
}
+ let draftErroring = false;
/**
* Save a draft update into the system via an AJAX request.
*/
function saveDraft() {
+ if (!$scope.draftsEnabled) return;
var data = {
name: $('#name').val(),
html: isMarkdown ? $sce.getTrustedHtml($scope.displayContent) : $scope.editContent
if (isMarkdown) data.markdown = $scope.editContent;
let url = window.baseUrl('/ajax/page/' + pageId + '/save-draft');
- $http.put(url, data).then((responseData) => {
+ $http.put(url, data).then(responseData => {
+ draftErroring = false;
var updateTime = moment.utc(moment.unix(responseData.data.timestamp)).toDate();
$scope.draftText = responseData.data.message + moment(updateTime).format('HH:mm');
if (!$scope.isNewPageDraft) $scope.isUpdateDraft = true;
showDraftSaveNotification();
+ lastSave = Date.now();
+ }, errorRes => {
+ if (draftErroring) return;
+ events.emit('error', 'Failed to save draft. Ensure you have internet connection before saving this page.')
+ draftErroring = true;
});
}
const pageId = Number($attrs.pageId);
$scope.tags = [];
-
+
$scope.sortOptions = {
handle: '.handle',
items: '> tr',
* Get all tags for the current book and add into scope.
*/
function getTags() {
- let url = window.baseUrl('/ajax/tags/get/page/' + pageId);
+ let url = window.baseUrl(`/ajax/tags/get/page/${pageId}`);
$http.get(url).then((responseData) => {
$scope.tags = responseData.data;
addEmptyTag();
}]);
-};
+ ngApp.controller('PageAttachmentController', ['$scope', '$http', '$attrs',
+ function ($scope, $http, $attrs) {
+ const pageId = $scope.uploadedTo = $attrs.pageId;
+ let currentOrder = '';
+ $scope.files = [];
+ $scope.editFile = false;
+ $scope.file = getCleanFile();
+ $scope.errors = {
+ link: {},
+ edit: {}
+ };
+ function getCleanFile() {
+ return {
+ page_id: pageId
+ };
+ }
+ // Angular-UI-Sort options
+ $scope.sortOptions = {
+ handle: '.handle',
+ items: '> tr',
+ containment: "parent",
+ axis: "y",
+ stop: sortUpdate,
+ };
+ /**
+ * Event listener for sort changes.
+ * Updates the file ordering on the server.
+ * @param event
+ * @param ui
+ */
+ function sortUpdate(event, ui) {
+ let newOrder = $scope.files.map(file => {return file.id}).join(':');
+ if (newOrder === currentOrder) return;
+
+ currentOrder = newOrder;
+ $http.put(`/files/sort/page/${pageId}`, {files: $scope.files}).then(resp => {
+ events.emit('success', resp.data.message);
+ }, checkError('sort'));
+ }
+ /**
+ * Used by dropzone to get the endpoint to upload to.
+ * @returns {string}
+ */
+ $scope.getUploadUrl = function (file) {
+ let suffix = (typeof file !== 'undefined') ? `/${file.id}` : '';
+ return window.baseUrl(`/files/upload${suffix}`);
+ };
+ /**
+ * Get files for the current page from the server.
+ */
+ function getFiles() {
+ let url = window.baseUrl(`/files/get/page/${pageId}`)
+ $http.get(url).then(resp => {
+ $scope.files = resp.data;
+ currentOrder = resp.data.map(file => {return file.id}).join(':');
+ }, checkError('get'));
+ }
+ getFiles();
+ /**
+ * Runs on file upload, Adds an file to local file list
+ * and shows a success message to the user.
+ * @param file
+ * @param data
+ */
+ $scope.uploadSuccess = function (file, data) {
+ $scope.$apply(() => {
+ $scope.files.push(data);
+ });
+ events.emit('success', 'File uploaded');
+ };
+ /**
+ * Upload and overwrite an existing file.
+ * @param file
+ * @param data
+ */
+ $scope.uploadSuccessUpdate = function (file, data) {
+ $scope.$apply(() => {
+ let search = filesIndexOf(data);
+ if (search !== -1) $scope.files[search] = data;
+ if ($scope.editFile) {
+ $scope.editFile = angular.copy(data);
+ data.link = '';
+ }
+ });
+ events.emit('success', 'File updated');
+ };
+ /**
+ * Delete a file from the server and, on success, the local listing.
+ * @param file
+ */
+ $scope.deleteFile = function(file) {
+ if (!file.deleting) {
+ file.deleting = true;
+ return;
+ }
+ $http.delete(`/files/${file.id}`).then(resp => {
+ events.emit('success', resp.data.message);
+ $scope.files.splice($scope.files.indexOf(file), 1);
+ }, checkError('delete'));
+ };
+ /**
+ * Attach a link to a page.
+ * @param fileName
+ * @param fileLink
+ */
+ $scope.attachLinkSubmit = function(file) {
+ file.uploaded_to = pageId;
+ $http.post('/files/link', file).then(resp => {
+ $scope.files.push(resp.data);
+ events.emit('success', 'Link attached');
+ $scope.file = getCleanFile();
+ }, checkError('link'));
+ };
+ /**
+ * Start the edit mode for a file.
+ * @param fileId
+ */
+ $scope.startEdit = function(file) {
+ console.log(file);
+ $scope.editFile = angular.copy(file);
+ $scope.editFile.link = (file.external) ? file.path : '';
+ };
+ /**
+ * Cancel edit mode
+ */
+ $scope.cancelEdit = function() {
+ $scope.editFile = false;
+ };
+ /**
+ * Update the name and link of a file.
+ * @param file
+ */
+ $scope.updateFile = function(file) {
+ $http.put(`/files/${file.id}`, file).then(resp => {
+ let search = filesIndexOf(resp.data);
+ if (search !== -1) $scope.files[search] = resp.data;
+ if ($scope.editFile && !file.external) {
+ $scope.editFile.link = '';
+ }
+ $scope.editFile = false;
+ events.emit('success', 'Attachment details updated');
+ }, checkError('edit'));
+ };
+ /**
+ * Get the url of a file.
+ */
+ $scope.getFileUrl = function(file) {
+ return window.baseUrl('/files/' + file.id);
+ }
+
+ /**
+ * Search the local files via another file object.
+ * Used to search via object copies.
+ * @param file
+ * @returns int
+ */
+ function filesIndexOf(file) {
+ for (let i = 0; i < $scope.files.length; i++) {
+ if ($scope.files[i].id == file.id) return i;
+ }
+ return -1;
+ }
+
+ /**
+ * Check for an error response in a ajax request.
+ * @param response
+ */
+ function checkError(errorGroupName) {
+ $scope.errors[errorGroupName] = {};
+ return function(response) {
+ if (typeof response.data !== 'undefined' && typeof response.data.error !== 'undefined') {
+ events.emit('error', response.data.error);
+ }
+ if (typeof response.data !== 'undefined' && typeof response.data.validation !== 'undefined') {
+ $scope.errors[errorGroupName] = response.data.validation;
+ console.log($scope.errors[errorGroupName])
+ }
+ }
+ }
+
+ }]);
+
+};
const DropZone = require('dropzone');
const markdown = require('marked');
-const toggleSwitchTemplate = require('./components/toggle-switch.html');
-const imagePickerTemplate = require('./components/image-picker.html');
-const dropZoneTemplate = require('./components/drop-zone.html');
-
module.exports = function (ngApp, events) {
/**
ngApp.directive('toggleSwitch', function () {
return {
restrict: 'A',
- template: toggleSwitchTemplate,
+ template: `
+ <div class="toggle-switch" ng-click="switch()" ng-class="{'active': isActive}">
+ <input type="hidden" ng-attr-name="{{name}}" ng-attr-value="{{value}}"/>
+ <div class="switch-handle"></div>
+ </div>
+ `,
scope: true,
link: function (scope, element, attrs) {
scope.name = attrs.name;
};
});
+ /**
+ * Common tab controls using simple jQuery functions.
+ */
+ ngApp.directive('tabContainer', function() {
+ return {
+ restrict: 'A',
+ link: function (scope, element, attrs) {
+ const $content = element.find('[tab-content]');
+ const $buttons = element.find('[tab-button]');
+
+ if (attrs.tabContainer) {
+ let initial = attrs.tabContainer;
+ $buttons.filter(`[tab-button="${initial}"]`).addClass('selected');
+ $content.hide().filter(`[tab-content="${initial}"]`).show();
+ } else {
+ $content.hide().first().show();
+ $buttons.first().addClass('selected');
+ }
+
+ $buttons.click(function() {
+ let clickedTab = $(this);
+ $buttons.removeClass('selected');
+ $content.hide();
+ let name = clickedTab.addClass('selected').attr('tab-button');
+ $content.filter(`[tab-content="${name}"]`).show();
+ });
+ }
+ };
+ });
+
+ /**
+ * Sub form component to allow inner-form sections to act like thier own forms.
+ */
+ ngApp.directive('subForm', function() {
+ return {
+ restrict: 'A',
+ link: function (scope, element, attrs) {
+ element.on('keypress', e => {
+ if (e.keyCode === 13) {
+ submitEvent(e);
+ }
+ });
+
+ element.find('button[type="submit"]').click(submitEvent);
+
+ function submitEvent(e) {
+ e.preventDefault()
+ if (attrs.subForm) scope.$eval(attrs.subForm);
+ }
+ }
+ };
+ });
+
/**
* Image Picker
ngApp.directive('imagePicker', ['$http', 'imageManagerService', function ($http, imageManagerService) {
return {
restrict: 'E',
- template: imagePickerTemplate,
+ template: `
+ <div class="image-picker">
+ <div>
+ <img ng-if="image && image !== 'none'" ng-src="{{image}}" ng-class="{{imageClass}}" alt="Image Preview">
+ <img ng-if="image === '' && defaultImage" ng-src="{{defaultImage}}" ng-class="{{imageClass}}" alt="Image Preview">
+ </div>
+ <button class="button" type="button" ng-click="showImageManager()">Select Image</button>
+ <br>
+
+ <button class="text-button" ng-click="reset()" type="button">Reset</button>
+ <span ng-show="showRemove" class="sep">|</span>
+ <button ng-show="showRemove" class="text-button neg" ng-click="remove()" type="button">Remove</button>
+
+ <input type="hidden" ng-attr-name="{{name}}" ng-attr-id="{{name}}" ng-attr-value="{{value}}">
+ </div>
+ `,
scope: {
name: '@',
resizeHeight: '@',
ngApp.directive('dropZone', [function () {
return {
restrict: 'E',
- template: dropZoneTemplate,
+ template: `
+ <div class="dropzone-container">
+ <div class="dz-message">Drop files or click here to upload</div>
+ </div>
+ `,
scope: {
uploadUrl: '@',
eventSuccess: '=',
uploadedTo: '@'
},
link: function (scope, element, attrs) {
+ if (attrs.placeholder) element[0].querySelector('.dz-message').textContent = attrs.placeholder;
var dropZone = new DropZone(element[0].querySelector('.dropzone-container'), {
url: scope.uploadUrl,
init: function () {
link: function (scope, elem, attrs) {
// Get common elements
- const $buttons = elem.find('[tab-button]');
- const $content = elem.find('[tab-content]');
+ const $buttons = elem.find('[toolbox-tab-button]');
+ const $content = elem.find('[toolbox-tab-content]');
const $toggle = elem.find('[toolbox-toggle]');
// Handle toolbox toggle click
function setActive(tabName, openToolbox) {
$buttons.removeClass('active');
$content.hide();
- $buttons.filter(`[tab-button="${tabName}"]`).addClass('active');
- $content.filter(`[tab-content="${tabName}"]`).show();
+ $buttons.filter(`[toolbox-tab-button="${tabName}"]`).addClass('active');
+ $content.filter(`[toolbox-tab-content="${tabName}"]`).show();
if (openToolbox) elem.addClass('open');
}
// Set the first tab content active on load
- setActive($content.first().attr('tab-content'), false);
+ setActive($content.first().attr('toolbox-tab-content'), false);
// Handle tab button click
$buttons.click(function (e) {
- let name = $(this).attr('tab-button');
+ let name = $(this).attr('toolbox-tab-button');
setActive(name, true);
});
}
let val = $input.val();
let url = $input.attr('autosuggest');
let type = $input.attr('autosuggest-type');
-
+
// Add name param to request if for a value
if (type.toLowerCase() === 'value') {
let $nameInput = $input.closest('tr').find('[autosuggest-type="name"]').first();
};
}]);
};
-
-
-
-
-
-
-
-
-
-
-
-
-
-
toolbar: "undo redo | styleselect | bold italic underline strikethrough superscript subscript | forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | table image-insert link hr | removeformat code fullscreen",
content_style: "body {padding-left: 15px !important; padding-right: 15px !important; margin:0!important; margin-left:auto!important;margin-right:auto!important;}",
style_formats: [
- {title: "Header 1", format: "h1"},
- {title: "Header 2", format: "h2"},
- {title: "Header 3", format: "h3"},
+ {title: "Header Large", format: "h2"},
+ {title: "Header Medium", format: "h3"},
+ {title: "Header Small", format: "h4"},
+ {title: "Header Tiny", format: "h5"},
{title: "Paragraph", format: "p", exact: true, classes: ''},
{title: "Blockquote", format: "blockquote"},
{title: "Code Block", icon: "code", format: "pre"},
}
}
-//body.ie .popup-body {
-// min-height: 100%;
-//}
-
.corner-button {
position: absolute;
top: 0;
min-height: 70vh;
}
-#image-manager .dropzone-container {
+.dropzone-container {
position: relative;
border: 3px dashed #DDD;
}
border-right: 6px solid transparent;
border-bottom: 6px solid $negative;
}
+
+
+[tab-container] .nav-tabs {
+ text-align: left;
+ border-bottom: 1px solid #DDD;
+ margin-bottom: $-m;
+ .tab-item {
+ padding: $-s;
+ color: #666;
+ &.selected {
+ border-bottom-width: 3px;
+ }
+ }
+}
\ No newline at end of file
.page-list {
- h3 {
+ h4 {
margin: $-l 0 $-xs 0;
font-size: 1.666em;
}
overflow: hidden;
margin-bottom: $-l;
}
- h4 {
+ h5 {
display: block;
margin: $-s 0 0 0;
border-left: 5px solid $color-page;
padding: $-xs 0 $-xs $-m;
+ font-size: 1.1em;
+ font-weight: normal;
&.draft {
border-left-color: $color-page-draft;
}
}
}
-.page-nav-list {
+.sidebar-page-nav {
$nav-indent: $-s;
- margin-left: 2px;
list-style: none;
+ margin: $-s 0 $-m 2px;
+ border-left: 2px dotted #BBB;
li {
- //border-left: 1px solid rgba(0, 0, 0, 0.1);
- padding-left: $-xs;
- border-left: 2px solid #888;
+ padding-left: $-s;
margin-bottom: 4px;
+ font-size: 0.95em;
}
- li a {
- color: #555;
+ .h1 {
+ margin-left: -2px;
}
- .nav-H2 {
+ .h2 {
+ margin-left: -2px;
+ }
+ .h3 {
margin-left: $nav-indent;
- font-size: 0.95em;
}
- .nav-H3 {
+ .h4 {
margin-left: $nav-indent*2;
- font-size: 0.90em
}
- .nav-H4 {
+ .h5 {
margin-left: $nav-indent*3;
- font-size: 0.85em
}
- .nav-H5 {
+ .h6 {
margin-left: $nav-indent*4;
- font-size: 0.80em
- }
- .nav-H6 {
- margin-left: $nav-indent*5;
- font-size: 0.75em
}
}
// Sidebar list
.book-tree {
- padding: $-l 0 0 0;
+ padding: $-xs 0 0 0;
position: relative;
right: 0;
top: 0;
}
.entity-list {
- >div {
+ > div {
padding: $-m 0;
}
- h3 {
+ h4 {
margin: 0;
}
p {
color: $color-page-draft;
}
}
+
.entity-list.compact {
font-size: 0.6em;
- h3, a {
+ h4, a {
line-height: 1.2;
}
p {
max-width: 100%;
height: auto !important;
}
+
+ // diffs
+ ins,
+ del {
+ text-decoration: none;
+ }
+ ins {
+ background: #dbffdb;
+ }
+ del {
+ background: #FFECEC;
+ }
}
// Page content pointers
background-color: #FFF;
border: 1px solid #DDD;
right: $-xl*2;
- z-index: 99;
width: 48px;
overflow: hidden;
align-items: stretch;
color: #444;
background-color: rgba(0, 0, 0, 0.1);
}
- div[tab-content] {
+ div[toolbox-tab-content] {
padding-bottom: 45px;
display: flex;
flex: 1;
min-height: 0px;
overflow-y: scroll;
}
- div[tab-content] .padded {
+ div[toolbox-tab-content] .padded {
flex: 1;
padding-top: 0;
}
padding-top: $-s;
position: relative;
}
- button.pos {
- position: absolute;
- bottom: 0;
- display: block;
- width: 100%;
- padding: $-s;
- height: 45px;
- border: 0;
- margin: 0;
- box-shadow: none;
- border-radius: 0;
- &:hover{
- box-shadow: none;
- }
- }
.handle {
user-select: none;
cursor: move;
}
}
-[tab-content] {
+[toolbox-tab-content] {
display: none;
}
vertical-align: middle;
padding: $-xs;
}
+}
+
+table.file-table {
+ @extend .no-style;
+ td {
+ padding: $-xs;
+ }
+ .ui-sortable-helper {
+ display: table;
+ }
}
\ No newline at end of file
margin-bottom: 0.43137255em;
}
h3 {
- font-size: 1.75em;
+ font-size: 2.333em;
line-height: 1.571428572em;
margin-top: 0.78571429em;
margin-bottom: 0.43137255em;
}
h4 {
- font-size: 1em;
+ font-size: 1.666em;
line-height: 1.375em;
margin-top: 0.78571429em;
margin-bottom: 0.43137255em;
}
-h1, h2, h3, h4 {
+h1, h2, h3, h4, h5, h6 {
font-weight: 400;
position: relative;
display: block;
color: #555;
.subheader {
- //display: block;
font-size: 0.5em;
line-height: 1em;
color: lighten($text-dark, 32%);
}
}
+h5 {
+ font-size: 1.4em;
+}
+
+h5, h6 {
+ font-weight: 500;
+ line-height: 1.2em;
+ margin-top: 0.78571429em;
+ margin-bottom: 0.66em;
+}
+
/*
* Link styling
*/
--- /dev/null
+<?php
+return [
+ /*
+ |--------------------------------------------------------------------------
+ | Authentication Language Lines
+ |--------------------------------------------------------------------------
+ |
+ | The following language lines are used during authentication for various
+ | messages that we need to display to the user. You are free to modify
+ | these language lines according to your application's requirements.
+ |
+ */
+ 'failed' => 'These credentials do not match our records.',
+ 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
+
+ /**
+ * Email Confirmation Text
+ */
+ 'email_confirm_subject' => 'Confirm your email on :appName',
+ 'email_confirm_greeting' => 'Thanks for joining :appName!',
+ 'email_confirm_text' => 'Please confirm your email address by clicking the button below:',
+ 'email_confirm_action' => 'Confirm Email',
+ 'email_confirm_send_error' => 'Email confirmation required but the system could not send the email. Contact the admin to ensure email is set up correctly.',
+ 'email_confirm_success' => 'Your email has been confirmed!',
+ 'email_confirm_resent' => 'Confirmation email resent, Please check your inbox.',
+];
\ No newline at end of file
--- /dev/null
+<?php
+
+return [
+
+ /**
+ * Settings text strings
+ * Contains all text strings used in the general settings sections of BookStack
+ * including users and roles.
+ */
+
+ 'settings' => 'Settings',
+ 'settings_save' => 'Save Settings',
+
+ 'app_settings' => 'App Settings',
+ 'app_name' => 'Application name',
+ 'app_name_desc' => 'This name is shown in the header and any emails.',
+ 'app_name_header' => 'Show Application name in header?',
+ 'app_public_viewing' => 'Allow public viewing?',
+ 'app_secure_images' => 'Enable higher security image uploads?',
+ 'app_secure_images_desc' => 'For performance reasons, all images are public. This option adds a random, hard-to-guess string in front of image urls. Ensure directory indexes are not enabled to prevent easy access.',
+ 'app_editor' => 'Page editor',
+ 'app_editor_desc' => 'Select which editor will be used by all users to edit pages.',
+ 'app_custom_html' => 'Custom HTML head content',
+ 'app_custom_html_desc' => 'Any content added here will be inserted into the bottom of the <head> section of every page. This is handy for overriding styles or adding analytics code.',
+ 'app_logo' => 'Application logo',
+ 'app_logo_desc' => 'This image should be 43px in height. <br>Large images will be scaled down.',
+ 'app_primary_color' => 'Application primary color',
+ 'app_primary_color_desc' => 'This should be a hex value. <br>Leave empty to reset to the default color.',
+
+ 'reg_settings' => 'Registration Settings',
+ 'reg_allow' => 'Allow registration?',
+ 'reg_default_role' => 'Default user role after registration',
+ 'reg_confirm_email' => 'Require email confirmation?',
+ 'reg_confirm_email_desc' => 'If domain restriction is used then email confirmation will be required and the below value will be ignored.',
+ 'reg_confirm_restrict_domain' => 'Restrict registration to domain',
+ 'reg_confirm_restrict_domain_desc' => 'Enter a comma separated list of email domains you would like to restrict registration to. Users will be sent an email to confirm their address before being allowed to interact with the application. <br> Note that users will be able to change their email addresses after successful registration.',
+ 'reg_confirm_restrict_domain_placeholder' => 'No restriction set',
+
+];
\ No newline at end of file
@if(setting('app-logo', '') !== 'none')
<img class="logo-image" src="{{ setting('app-logo', '') === '' ? baseUrl('/logo.png') : baseUrl(setting('app-logo', '')) }}" alt="Logo">
@endif
- <span class="logo-text">{{ setting('app-name') }}</span>
+ @if (setting('app-name-header'))
+ <span class="logo-text">{{ setting('app-name') }}</span>
+ @endif
</a>
</div>
<div class="col-lg-4 col-sm-3 text-center">
<div class="book entity-list-item" data-entity-type="book" data-entity-id="{{$book->id}}">
- <h3 class="text-book"><a class="text-book entity-list-item-link" href="{{$book->getUrl()}}"><i class="zmdi zmdi-book"></i><span class="entity-list-item-name">{{$book->name}}</span></a></h3>
+ <h4 class="text-book"><a class="text-book entity-list-item-link" href="{{$book->getUrl()}}"><i class="zmdi zmdi-book"></i><span class="entity-list-item-name">{{$book->name}}</span></a></h4>
@if(isset($book->searchSnippet))
<p class="text-muted">{!! $book->searchSnippet !!}</p>
@else
<div class="chapter entity-list-item" data-entity-type="chapter" data-entity-id="{{$chapter->id}}">
- <h3>
+ <h4>
@if (isset($showPath) && $showPath)
<a href="{{ $chapter->book->getUrl() }}" class="text-book">
<i class="zmdi zmdi-book"></i>{{ $chapter->book->name }}
<a href="{{ $chapter->getUrl() }}" class="text-chapter entity-list-item-link">
<i class="zmdi zmdi-collection-bookmark"></i><span class="entity-list-item-name">{{ $chapter->name }}</span>
</a>
- </h3>
+ </h4>
@if(isset($chapter->searchSnippet))
<p class="text-muted">{!! $chapter->searchSnippet !!}</p>
@else
<p class="text-muted chapter-toggle"><i class="zmdi zmdi-caret-right"></i> <i class="zmdi zmdi-file-text"></i> <span>{{ count($chapter->pages) }} Pages</span></p>
<div class="inset-list">
@foreach($chapter->pages as $page)
- <h4 class="@if($page->draft) draft @endif"><a href="{{ $page->getUrl() }}" class="text-page @if($page->draft) draft @endif"><i class="zmdi zmdi-file-text"></i>{{$page->name}}</a></h4>
+ <h5 class="@if($page->draft) draft @endif"><a href="{{ $page->getUrl() }}" class="text-page @if($page->draft) draft @endif"><i class="zmdi zmdi-file-text"></i>{{$page->name}}</a></h5>
@endforeach
</div>
@endif
+++ /dev/null
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"
- style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;">
-
-<head style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;">
- <meta name="viewport" content="width=device-width"
- style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;"/>
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"
- style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;"/>
- <title>Confirm Your Email At {{ setting('app-name')}}</title>
- <style style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;">
- * {
- margin: 0;
- padding: 0;
- font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;
- font-size: 100%;
- line-height: 1.6;
- }
-
- img {
- max-width: 100%;
- }
-
- body {
- -webkit-font-smoothing: antialiased;
- -webkit-text-size-adjust: none;
- width: 100% !important;
- height: 100%;
- }
-
- a {
- color: #348eda;
- }
-
- .btn-primary {
- text-decoration: none;
- color: #FFF;
- background-color: #348eda;
- border: solid #348eda;
- border-width: 10px 20px;
- line-height: 2;
- font-weight: bold;
- margin-right: 10px;
- text-align: center;
- cursor: pointer;
- display: inline-block;
- border-radius: 4px;
- }
-
- .btn-secondary {
- text-decoration: none;
- color: #FFF;
- background-color: #aaa;
- border: solid #aaa;
- border-width: 10px 20px;
- line-height: 2;
- font-weight: bold;
- margin-right: 10px;
- text-align: center;
- cursor: pointer;
- display: inline-block;
- border-radius: 25px;
- }
-
- .last {
- margin-bottom: 0;
- }
-
- .first {
- margin-top: 0;
- }
-
- .padding {
- padding: 10px 0;
- }
-
- table.body-wrap {
- width: 100%;
- padding: 20px;
- }
-
- table.body-wrap .container {
- border: 1px solid #f0f0f0;
- }
-
- h1,
- h2,
- h3 {
- font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
- color: #444;
- margin: 10px 0 10px;
- line-height: 1.2;
- font-weight: 200;
- }
-
- h1 {
- font-size: 36px;
- }
-
- h2 {
- font-size: 28px;
- }
-
- h3 {
- font-size: 22px;
- }
-
- p,
- ul,
- ol {
- margin-bottom: 10px;
- font-weight: normal;
- font-size: 14px;
- color: #888888;
- }
-
- ul li,
- ol li {
- margin-left: 5px;
- list-style-position: inside;
- }
-
- .container {
- display: block !important;
- max-width: 600px !important;
- margin: 0 auto !important;
- clear: both !important;
- }
-
- .body-wrap .container {
- padding: 20px;
- }
-
- .content {
- max-width: 600px;
- margin: 0 auto;
- display: block;
- }
-
- .content table {
- width: 100%;
- }
- </style>
-</head>
-
-<body bgcolor="#f6f6f6"
- style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;-webkit-font-smoothing:antialiased;-webkit-text-size-adjust:none;width:100%!important;height:100%;">
-<!-- body -->
-<table class="body-wrap" bgcolor="#f6f6f6"
- style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;width:100%;padding-top:20px;padding-bottom:20px;padding-right:20px;padding-left:20px;">
- <tr style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;">
- <td style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;"></td>
- <td class="container" bgcolor="#FFFFFF"
- style="font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;display:block!important;max-width:600px!important;margin-top:0 !important;margin-bottom:0 !important;margin-right:auto !important;margin-left:auto !important;clear:both!important;padding-top:20px;padding-bottom:20px;padding-right:20px;padding-left:20px;border-width:1px;border-style:solid;border-color:#f0f0f0;">
- <!-- content -->
- <div class="content"
- style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;max-width:600px;margin-top:0;margin-bottom:0;margin-right:auto;margin-left:auto;display:block;">
- <table style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;width:100%;">
- <tr style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;">
- <td style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;">
- <h1 style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif;color:#444;margin-top:10px;margin-bottom:10px;margin-right:0;margin-left:0;line-height:1.2;font-weight:200;font-size:36px;">
- Email Confirmation</h1>
- <p style="margin-top:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;line-height:1.6;margin-bottom:10px;font-weight:normal;font-size:14px;color:#888888;">
- Thanks for joining <a href="{{ baseUrl('/', true) }}">{{ setting('app-name')}}</a>. <br/>
- Please confirm your email address by clicking the button below.</p>
- <table style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;width:100%;">
- <tr style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;">
- <td class="padding"
- style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;padding-top:10px;padding-bottom:10px;padding-right:0;padding-left:0;">
- <p style="margin-top:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;line-height:1.6;margin-bottom:10px;font-weight:normal;font-size:14px;color:#888888;">
- <a class="btn-primary" href="{{ baseUrl('/register/confirm/' . $token, true) }}"
- style="margin-top:0;margin-bottom:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;text-decoration:none;color:#FFF;background-color:#348eda;border-style:solid;border-color:#348eda;border-width:10px 20px;line-height:2;font-weight:bold;margin-right:10px;text-align:center;cursor:pointer;display:inline-block;border-radius:4px;">Confirm
- Email</a></p>
- </td>
- </tr>
- </table>
- </td>
- </tr>
- </table>
- </div>
- <!-- /content -->
- </td>
- <td style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;"></td>
- </tr>
-</table>
-<!-- /body -->
-</body>
-
-</html>
+++ /dev/null
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"\r "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\r<html xmlns="http://www.w3.org/1999/xhtml"\r style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;">\r\r<head style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;">\r <meta name="viewport" content="width=device-width"\r style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;"/>\r <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"\r style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;"/>\r <title>Password Reset From {{ setting('app-name')}}</title>\r <style style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;">\r * {\r margin: 0;\r padding: 0;\r font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;\r font-size: 100%;\r line-height: 1.6;\r }\r\r img {\r max-width: 100%;\r }\r\r body {\r -webkit-font-smoothing: antialiased;\r -webkit-text-size-adjust: none;\r width: 100% !important;\r height: 100%;\r }\r\r a {\r color: #348eda;\r }\r\r .btn-primary {\r text-decoration: none;\r color: #FFF;\r background-color: #348eda;\r border: solid #348eda;\r border-width: 10px 20px;\r line-height: 2;\r font-weight: bold;\r margin-right: 10px;\r text-align: center;\r cursor: pointer;\r display: inline-block;\r border-radius: 4px;\r }\r\r .btn-secondary {\r text-decoration: none;\r color: #FFF;\r background-color: #aaa;\r border: solid #aaa;\r border-width: 10px 20px;\r line-height: 2;\r font-weight: bold;\r margin-right: 10px;\r text-align: center;\r cursor: pointer;\r display: inline-block;\r border-radius: 25px;\r }\r\r .last {\r margin-bottom: 0;\r }\r\r .first {\r margin-top: 0;\r }\r\r .padding {\r padding: 10px 0;\r }\r\r table.body-wrap {\r width: 100%;\r padding: 20px;\r }\r\r table.body-wrap .container {\r border: 1px solid #f0f0f0;\r }\r\r h1,\r h2,\r h3 {\r font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;\r color: #444;\r margin: 10px 0 10px;\r line-height: 1.2;\r font-weight: 200;\r }\r\r h1 {\r font-size: 36px;\r }\r\r h2 {\r font-size: 28px;\r }\r\r h3 {\r font-size: 22px;\r }\r\r p,\r ul,\r ol {\r margin-bottom: 10px;\r font-weight: normal;\r font-size: 14px;\r color: #888888;\r }\r\r ul li,\r ol li {\r margin-left: 5px;\r list-style-position: inside;\r }\r\r .container {\r display: block !important;\r max-width: 600px !important;\r margin: 0 auto !important;\r clear: both !important;\r }\r\r .body-wrap .container {\r padding: 20px;\r }\r\r .content {\r max-width: 600px;\r margin: 0 auto;\r display: block;\r }\r\r .content table {\r width: 100%;\r }\r </style>\r</head>\r\r<body bgcolor="#f6f6f6"\r style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;-webkit-font-smoothing:antialiased;-webkit-text-size-adjust:none;width:100%!important;height:100%;">\r<!-- body -->\r<table class="body-wrap" bgcolor="#f6f6f6"\r style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;width:100%;padding-top:20px;padding-bottom:20px;padding-right:20px;padding-left:20px;">\r <tr style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;">\r <td style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;"></td>\r <td class="container" bgcolor="#FFFFFF"\r style="font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;display:block!important;max-width:600px!important;margin-top:0 !important;margin-bottom:0 !important;margin-right:auto !important;margin-left:auto !important;clear:both!important;padding-top:20px;padding-bottom:20px;padding-right:20px;padding-left:20px;border-width:1px;border-style:solid;border-color:#f0f0f0;">\r <!-- content -->\r <div class="content"\r style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;max-width:600px;margin-top:0;margin-bottom:0;margin-right:auto;margin-left:auto;display:block;">\r <table style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;width:100%;">\r <tr style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;">\r <td style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;">\r <h1 style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif;color:#444;margin-top:10px;margin-bottom:10px;margin-right:0;margin-left:0;line-height:1.2;font-weight:200;font-size:36px;">\r Password Reset</h1>\r <p style="margin-top:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;line-height:1.6;margin-bottom:10px;font-weight:normal;font-size:14px;color:#888888;">\r A password reset was requested for this email address on <a\r href="{{ baseUrl('/', true) }}">{{ setting('app-name')}}</a>. If you did not request\r a password change please ignore this email.</p>\r <table style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;width:100%;">\r <tr style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;">\r <td class="padding"\r style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;padding-top:10px;padding-bottom:10px;padding-right:0;padding-left:0;">\r <p style="margin-top:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;line-height:1.6;margin-bottom:10px;font-weight:normal;font-size:14px;color:#888888;">\r <a class="btn-primary" href="{{ baseUrl('/password/reset/' . $token, true) }}"\r style="margin-top:0;margin-bottom:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;text-decoration:none;color:#FFF;background-color:#348eda;border-style:solid;border-color:#348eda;border-width:10px 20px;line-height:2;font-weight:bold;margin-right:10px;text-align:center;cursor:pointer;display:inline-block;border-radius:4px;">Click\r here to reset your password</a></p>\r </td>\r </tr>\r </table>\r </td>\r </tr>\r </table>\r </div>\r <!-- /content -->\r </td>\r <td style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;"></td>\r </tr>\r</table>\r<!-- /body -->\r</body>\r\r</html>\r
\ No newline at end of file
<div class="col-sm-4">
<div id="recent-drafts">
@if(count($draftPages) > 0)
- <h3>My Recent Drafts</h3>
+ <h4>My Recent Drafts</h4>
@include('partials/entity-list', ['entities' => $draftPages, 'style' => 'compact'])
@endif
</div>
@if($signedIn)
- <h3>My Recently Viewed</h3>
+ <h4>My Recently Viewed</h4>
@else
- <h3>Recent Books</h3>
+ <h4>Recent Books</h4>
@endif
@include('partials/entity-list', [
'entities' => $recents,
</div>
<div class="col-sm-4">
- <h3><a class="no-color" href="{{ baseUrl("/pages/recently-created") }}">Recently Created Pages</a></h3>
+ <h4><a class="no-color" href="{{ baseUrl("/pages/recently-created") }}">Recently Created Pages</a></h4>
<div id="recently-created-pages">
@include('partials/entity-list', [
'entities' => $recentlyCreatedPages,
])
</div>
- <h3><a class="no-color" href="{{ baseUrl("/pages/recently-updated") }}">Recently Updated Pages</a></h3>
+ <h4><a class="no-color" href="{{ baseUrl("/pages/recently-updated") }}">Recently Updated Pages</a></h4>
<div id="recently-updated-pages">
@include('partials/entity-list', [
'entities' => $recentlyUpdatedPages,
</div>
<div class="col-sm-4" id="recent-activity">
- <h3>Recent Activity</h3>
+ <h4>Recent Activity</h4>
@include('partials/activity-list', ['activity' => $activity])
</div>
@include('partials/image-manager', ['imageType' => 'gallery', 'uploaded_to' => $page->id])
@include('partials/entity-selector-popup')
- <script>
- (function() {
-
- })();
- </script>
-
@stop
\ No newline at end of file
<div class="tabs primary-background-light">
<span toolbox-toggle><i class="zmdi zmdi-caret-left-circle"></i></span>
- <span tab-button="tags" title="Page Tags" class="active"><i class="zmdi zmdi-tag"></i></span>
+ <span toolbox-tab-button="tags" title="Page Tags" class="active"><i class="zmdi zmdi-tag"></i></span>
+ @if(userCan('file-create-all'))
+ <span toolbox-tab-button="files" title="Attachments"><i class="zmdi zmdi-attachment"></i></span>
+ @endif
</div>
- <div tab-content="tags" ng-controller="PageTagController" page-id="{{ $page->id or 0 }}">
+ <div toolbox-tab-content="tags" ng-controller="PageTagController" page-id="{{ $page->id or 0 }}">
<h4>Page Tags</h4>
<div class="padded tags">
<p class="muted small">Add some tags to better categorise your content. <br> You can assign a value to a tag for more in-depth organisation.</p>
</div>
</div>
+ @if(userCan('file-create-all'))
+ <div toolbox-tab-content="files" ng-controller="PageAttachmentController" page-id="{{ $page->id or 0 }}">
+ <h4>Attachments</h4>
+ <div class="padded files">
+
+ <div id="file-list" ng-show="!editFile">
+ <p class="muted small">Upload some files or attach some link to display on your page. This are visible in the page sidebar.</p>
+
+ <div tab-container>
+ <div class="nav-tabs">
+ <div tab-button="list" class="tab-item">File List</div>
+ <div tab-button="file" class="tab-item">Upload File</div>
+ <div tab-button="link" class="tab-item">Attach Link</div>
+ </div>
+ <div tab-content="list">
+ <table class="file-table" style="width: 100%;">
+ <tbody ui-sortable="sortOptions" ng-model="files" >
+ <tr ng-repeat="file in files track by $index">
+ <td width="20" ><i class="handle zmdi zmdi-menu"></i></td>
+ <td>
+ <a ng-href="@{{getFileUrl(file)}}" target="_blank" ng-bind="file.name"></a>
+ <div ng-if="file.deleting">
+ <span class="neg small">Click delete again to confirm you want to delete this attachment.</span>
+ <br>
+ <span class="text-primary small" ng-click="file.deleting=false;">Cancel</span>
+ </div>
+ </td>
+ <td width="10" ng-click="startEdit(file)" class="text-center text-primary" style="padding: 0;"><i class="zmdi zmdi-edit"></i></td>
+ <td width="5"></td>
+ <td width="10" ng-click="deleteFile(file)" class="text-center text-neg" style="padding: 0;"><i class="zmdi zmdi-close"></i></td>
+ </tr>
+ </tbody>
+ </table>
+ <p class="small muted" ng-if="files.length == 0">
+ No files have been uploaded.
+ </p>
+ </div>
+ <div tab-content="file">
+ <drop-zone upload-url="@{{getUploadUrl()}}" uploaded-to="@{{uploadedTo}}" event-success="uploadSuccess"></drop-zone>
+ </div>
+ <div tab-content="link" sub-form="attachLinkSubmit(file)">
+ <p class="muted small">You can attach a link if you'd prefer not to upload a file. This can be a link to another page or a link to a file in the cloud.</p>
+ <div class="form-group">
+ <label for="attachment-via-link">Link Name</label>
+ <input type="text" placeholder="Link name" ng-model="file.name">
+ <p class="small neg" ng-repeat="error in errors.link.name" ng-bind="error"></p>
+ </div>
+ <div class="form-group">
+ <label for="attachment-via-link">Link to file</label>
+ <input type="text" placeholder="Url of site or file" ng-model="file.link">
+ <p class="small neg" ng-repeat="error in errors.link.link" ng-bind="error"></p>
+ </div>
+ <button type="submit" class="button pos">Attach</button>
+
+ </div>
+ </div>
+
+ </div>
+
+ <div id="file-edit" ng-if="editFile" sub-form="updateFile(editFile)">
+ <h5>Edit File</h5>
+
+ <div class="form-group">
+ <label for="attachment-name-edit">File Name</label>
+ <input type="text" id="attachment-name-edit" placeholder="File name" ng-model="editFile.name">
+ <p class="small neg" ng-repeat="error in errors.edit.name" ng-bind="error"></p>
+ </div>
+
+ <div tab-container="@{{ editFile.external ? 'link' : 'file' }}">
+ <div class="nav-tabs">
+ <div tab-button="file" class="tab-item">Upload File</div>
+ <div tab-button="link" class="tab-item">Set Link</div>
+ </div>
+ <div tab-content="file">
+ <drop-zone upload-url="@{{getUploadUrl(editFile)}}" uploaded-to="@{{uploadedTo}}" placeholder="Drop files or click here to upload and overwrite" event-success="uploadSuccessUpdate"></drop-zone>
+ <br>
+ </div>
+ <div tab-content="link">
+ <div class="form-group">
+ <label for="attachment-link-edit">Link to file</label>
+ <input type="text" id="attachment-link-edit" placeholder="Attachment link" ng-model="editFile.link">
+ <p class="small neg" ng-repeat="error in errors.edit.link" ng-bind="error"></p>
+ </div>
+ </div>
+ </div>
+
+ <button type="button" class="button" ng-click="cancelEdit()">Back</button>
+ <button type="submit" class="button pos">Save</button>
+ </div>
+
+ </div>
+ </div>
+ @endif
+
</div>
\ No newline at end of file
-<div class="page-editor flex-fill flex" ng-controller="PageEditController" editor-type="{{ setting('app-editor') }}" page-id="{{ $model->id or 0 }}" page-new-draft="{{ $model->draft or 0 }}" page-update-draft="{{ $model->isDraft or 0 }}">
+<div class="page-editor flex-fill flex" ng-controller="PageEditController" drafts-enabled="{{ $draftsEnabled ? 'true' : 'false' }}" editor-type="{{ setting('app-editor') }}" page-id="{{ $model->id or 0 }}" page-new-draft="{{ $model->draft or 0 }}" page-update-draft="{{ $model->isDraft or 0 }}">
{{ csrf_field() }}
+
+ {{--Header Bar--}}
<div class="faded-small toolbar">
<div class="container">
<div class="row">
</div>
<div class="col-sm-4 faded text-center">
- <div dropdown class="dropdown-container draft-display">
+ <div ng-show="draftsEnabled" dropdown class="dropdown-container draft-display">
<a dropdown-toggle class="text-primary text-button"><span class="faded-text" ng-bind="draftText"></span> <i class="zmdi zmdi-more-vert"></i></a>
<i class="zmdi zmdi-check-circle text-pos draft-notification" ng-class="{visible: draftUpdated}"></i>
<ul>
</div>
</div>
+ {{--Title input--}}
<div class="title-input page-title clearfix" ng-non-bindable>
<div class="input">
@include('form/text', ['name' => 'name', 'placeholder' => 'Page Title'])
</div>
</div>
+ {{--Editors--}}
<div class="edit-area flex-fill flex">
+
+ {{--WYSIWYG Editor--}}
@if(setting('app-editor') === 'wysiwyg')
<div tinymce="editorOptions" mce-change="editorChange" mce-model="editContent" class="flex-fill flex">
<textarea id="html-editor" name="html" rows="5" ng-non-bindable
@endif
@endif
+ {{--Markdown Editor--}}
@if(setting('app-editor') === 'markdown')
<div id="markdown-editor" markdown-editor class="flex-fill flex">
@if($errors->has('markdown'))
<div class="text-neg text-small">{{ $errors->first('markdown') }}</div>
@endif
-
@endif
+
</div>
</div>
\ No newline at end of file
--- /dev/null
+@extends('base')
+
+@section('content')
+
+ <div class="container small" ng-non-bindable>
+ <h1>Create Page</h1>
+ <form action="{{ $parent->getUrl('/page/create/guest') }}" method="POST">
+
+ {!! csrf_field() !!}
+
+ <div class="form-group title-input">
+ <label for="name">Page Name</label>
+ @include('form/text', ['name' => 'name'])
+ </div>
+
+ <div class="form-group">
+ <a href="{{ $parent->getUrl() }}" class="button muted">Cancel</a>
+ <button type="submit" class="button pos">Continue</button>
+ </div>
+
+ </form>
+ </div>
+
+
+@stop
\ No newline at end of file
<div class="page {{$page->draft ? 'draft' : ''}} entity-list-item" data-entity-type="page" data-entity-id="{{$page->id}}">
- <h3>
+ <h4>
<a href="{{ $page->getUrl() }}" class="text-page entity-list-item-link"><i class="zmdi zmdi-file-text"></i><span class="entity-list-item-name">{{ $page->name }}</span></a>
- </h3>
+ </h4>
@if(isset($page->searchSnippet))
<p class="text-muted">{!! $page->searchSnippet !!}</p>
<div style="clear:left;"></div>
- {!! $page->html !!}
+ @if (isset($diff) && $diff)
+ {!! $diff !!}
+ @else
+ {!! $page->html !!}
+ @endif
</div>
\ No newline at end of file
<table class="table">
<tr>
- <th width="25%">Name</th>
- <th colspan="2" width="10%">Created By</th>
+ <th width="23%">Name</th>
+ <th colspan="2" width="8%">Created By</th>
<th width="15%">Revision Date</th>
<th width="25%">Changelog</th>
- <th width="15%">Actions</th>
+ <th width="20%">Actions</th>
</tr>
@foreach($page->revisions as $index => $revision)
<tr>
<td> @if($revision->createdBy) {{ $revision->createdBy->name }} @else Deleted User @endif</td>
<td><small>{{ $revision->created_at->format('jS F, Y H:i:s') }} <br> ({{ $revision->created_at->diffForHumans() }})</small></td>
<td>{{ $revision->summary }}</td>
- @if ($index !== 0)
- <td>
+ <td>
+ <a href="{{ $revision->getUrl('changes') }}" target="_blank">Changes</a>
+ <span class="text-muted"> | </span>
+
+ @if ($index === 0)
+ <a target="_blank" href="{{ $page->getUrl() }}"><i>Current Version</i></a>
+ @else
<a href="{{ $revision->getUrl() }}" target="_blank">Preview</a>
<span class="text-muted"> | </span>
- <a href="{{ $revision->getUrl() }}/restore">Restore</a>
- </td>
- @else
- <td><a target="_blank" href="{{ $page->getUrl() }}"><i>Current Version</i></a></td>
- @endif
+ <a href="{{ $revision->getUrl('restore') }}" target="_blank">Restore</a>
+ @endif
+ </td>
</tr>
@endforeach
</table>
@endif
</div>
@endif
- @include('pages/sidebar-tree-list', ['book' => $book, 'sidebarTree' => $sidebarTree])
+
+ @include('pages/sidebar-tree-list', ['book' => $book, 'sidebarTree' => $sidebarTree, 'pageNav' => $pageNav])
</div>
</div>
<div class="book-tree" ng-non-bindable>
+
+ @if (isset($page) && $page->files->count() > 0)
+ <h6 class="text-muted">Attachments</h6>
+ @foreach($page->files as $file)
+ <div class="attachment">
+ <a href="{{ $file->getUrl() }}" @if($file->external) target="_blank" @endif><i class="zmdi zmdi-{{ $file->external ? 'open-in-new' : 'file' }}"></i> {{ $file->name }}</a>
+ </div>
+ @endforeach
+ @endif
+
+ @if (isset($pageNav) && $pageNav)
+ <h6 class="text-muted">Page Navigation</h6>
+ <div class="sidebar-page-nav menu">
+ @foreach($pageNav as $navItem)
+ <li class="page-nav-item {{ $navItem['nodeName'] }}">
+ <a href="{{ $navItem['link'] }}">{{ $navItem['text'] }}</a>
+ </li>
+ @endforeach
+ </div>
+ @endif
+
<h6 class="text-muted">Book Navigation</h6>
<ul class="sidebar-page-list menu">
<li class="book-header"><a href="{{ $book->getUrl() }}" class="book {{ $current->matches($book)? 'selected' : '' }}"><i class="zmdi zmdi-book"></i>{{$book->name}}</a></li>
.nav-tabs a.selected, .nav-tabs .tab-item.selected {
border-bottom-color: {{ setting('app-color') }};
}
- p.primary:hover, p .primary:hover, span.primary:hover, .text-primary:hover, a, a:hover, a:focus, .text-button, .text-button:hover, .text-button:focus {
+ .text-primary, p.primary, p .primary, span.primary:hover, .text-primary:hover, a, a:hover, a:focus, .text-button, .text-button:hover, .text-button:focus {
color: {{ setting('app-color') }};
}
</style>
\ No newline at end of file
@if(setting('app-logo', '') !== 'none')
<img class="logo-image" src="{{ setting('app-logo', '') === '' ? baseUrl('/logo.png') : baseUrl(setting('app-logo', '')) }}" alt="Logo">
@endif
- <span class="logo-text">{{ setting('app-name') }}</span>
+ @if (setting('app-name-header'))
+ <span class="logo-text">{{ setting('app-name') }}</span>
+ @endif
</a>
</div>
<div class="col-md-6">
<div class="container small settings-container">
- <h1>Settings</h1>
+ <h1>{{ trans('settings.settings') }}</h1>
<form action="{{ baseUrl("/settings") }}" method="POST" ng-cloak>
{!! csrf_field() !!}
<h3>App Settings</h3>
<div class="row">
+
<div class="col-md-6">
<div class="form-group">
- <label for="setting-app-name">Application name</label>
+ <label for="setting-app-name">{{ trans('settings.app_name') }}</label>
+ <p class="small">{{ trans('settings.app_name_desc') }}</p>
<input type="text" value="{{ setting('app-name', 'BookStack') }}" name="setting-app-name" id="setting-app-name">
</div>
<div class="form-group">
- <label>Allow public viewing?</label>
+ <label>{{ trans('settings.app_name_header') }}</label>
+ <div toggle-switch name="setting-app-name-header" value="{{ setting('app-name-header') }}"></div>
+ </div>
+ <div class="form-group">
+ <label for="setting-app-public">{{ trans('settings.app_public_viewing') }}</label>
<div toggle-switch name="setting-app-public" value="{{ setting('app-public') }}"></div>
</div>
<div class="form-group">
- <label>Enable higher security image uploads?</label>
- <p class="small">For performance reasons, all images are public. This option adds a random, hard-to-guess string in front of image urls. Ensure directory indexes are not enabled to prevent easy access.</p>
+ <label>{{ trans('settings.app_secure_images') }}</label>
+ <p class="small">{{ trans('settings.app_secure_images_desc') }}</p>
<div toggle-switch name="setting-app-secure-images" value="{{ setting('app-secure-images') }}"></div>
</div>
<div class="form-group">
- <label for="setting-app-editor">Page editor</label>
- <p class="small">Select which editor will be used by all users to edit pages.</p>
+ <label for="setting-app-editor">{{ trans('settings.app_editor') }}</label>
+ <p class="small">{{ trans('settings.app_editor_desc') }}</p>
<select name="setting-app-editor" id="setting-app-editor">
<option @if(setting('app-editor') === 'wysiwyg') selected @endif value="wysiwyg">WYSIWYG</option>
<option @if(setting('app-editor') === 'markdown') selected @endif value="markdown">Markdown</option>
</select>
</div>
</div>
+
<div class="col-md-6">
<div class="form-group" id="logo-control">
- <label for="setting-app-logo">Application logo</label>
- <p class="small">This image should be 43px in height. <br>Large images will be scaled down.</p>
+ <label for="setting-app-logo">{{ trans('settings.app_logo') }}</label>
+ <p class="small">{!! trans('settings.app_logo_desc') !!}</p>
<image-picker resize-height="43" show-remove="true" resize-width="200" current-image="{{ setting('app-logo', '') }}" default-image="{{ baseUrl('/logo.png') }}" name="setting-app-logo" image-class="logo-image"></image-picker>
</div>
<div class="form-group" id="color-control">
- <label for="setting-app-color">Application primary color</label>
- <p class="small">This should be a hex value. <br> Leave empty to reset to the default color.</p>
+ <label for="setting-app-color">{{ trans('settings.app_primary_color') }}</label>
+ <p class="small">{!! trans('settings.app_primary_color_desc') !!}</p>
<input type="text" value="{{ setting('app-color', '') }}" name="setting-app-color" id="setting-app-color" placeholder="#0288D1">
<input type="hidden" value="{{ setting('app-color-light', '') }}" name="setting-app-color-light" id="setting-app-color-light" placeholder="rgba(21, 101, 192, 0.15)">
</div>
</div>
+
</div>
+
<div class="form-group">
- <label for="setting-app-custom-head">Custom HTML head content</label>
- <p class="small">Any content added here will be inserted into the bottom of the <head> section of every page. This is handy for overriding styles or adding analytics code.</p>
+ <label for="setting-app-custom-head">{{ trans('settings.app_custom_html') }}</label>
+ <p class="small">{{ trans('settings.app_custom_html_desc') }}</p>
<textarea name="setting-app-custom-head" id="setting-app-custom-head">{{ setting('app-custom-head', '') }}</textarea>
</div>
<hr class="margin-top">
- <h3>Registration Settings</h3>
+ <h3>{{ trans('settings.reg_settings') }}</h3>
<div class="row">
<div class="col-md-6">
<div class="form-group">
- <label for="setting-registration-enabled">Allow registration?</label>
+ <label for="setting-registration-enabled">{{ trans('settings.reg_allow') }}</label>
<div toggle-switch name="setting-registration-enabled" value="{{ setting('registration-enabled') }}"></div>
</div>
<div class="form-group">
- <label for="setting-registration-role">Default user role after registration</label>
+ <label for="setting-registration-role">{{ trans('settings.reg_default_role') }}</label>
<select id="setting-registration-role" name="setting-registration-role" @if($errors->has('setting-registration-role')) class="neg" @endif>
- @foreach(\BookStack\Role::visible() as $role)
+ @foreach(\BookStack\Role::all() as $role)
<option value="{{$role->id}}" data-role-name="{{ $role->name }}"
@if(setting('registration-role', \BookStack\Role::first()->id) == $role->id) selected @endif
>
</select>
</div>
<div class="form-group">
- <label for="setting-registration-confirmation">Require email confirmation?</label>
- <p class="small">If domain restriction is used then email confirmation will be required and the below value will be ignored.</p>
+ <label for="setting-registration-confirmation">{{ trans('settings.reg_confirm_email') }}</label>
+ <p class="small">{{ trans('settings.reg_confirm_email_desc') }}</p>
<div toggle-switch name="setting-registration-confirmation" value="{{ setting('registration-confirmation') }}"></div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
- <label for="setting-registration-restrict">Restrict registration to domain</label>
- <p class="small">Enter a comma separated list of email domains you would like to restrict registration to. Users will be sent an email to confirm their address before being allowed to interact with the application.
- <br> Note that users will be able to change their email addresses after successful registration.</p>
- <input type="text" id="setting-registration-restrict" name="setting-registration-restrict" placeholder="No restriction set" value="{{ setting('registration-restrict', '') }}">
+ <label for="setting-registration-restrict">{{ trans('settings.reg_confirm_restrict_domain') }}</label>
+ <p class="small">{!! trans('settings.reg_confirm_restrict_domain_desc') !!}</p>
+ <input type="text" id="setting-registration-restrict" name="setting-registration-restrict" placeholder="{{ trans('settings.reg_confirm_restrict_domain_placeholder') }}" value="{{ setting('registration-restrict', '') }}">
</div>
</div>
</div>
<span class="float right muted">
BookStack @if(strpos($version, 'v') !== 0) version @endif {{ $version }}
</span>
- <button type="submit" class="button pos">Save Settings</button>
+ <button type="submit" class="button pos">{{ trans('settings.settings_save') }}</button>
</div>
</form>
<label>@include('settings/roles/checkbox', ['permission' => 'image-delete-all']) All</label>
</td>
</tr>
+ <tr>
+ <td>Attached <br>Files</td>
+ <td>@include('settings/roles/checkbox', ['permission' => 'file-create-all'])</td>
+ <td style="line-height:1.2;"><small class="faded">Controlled by the asset they are uploaded to</small></td>
+ <td>
+ <label>@include('settings/roles/checkbox', ['permission' => 'file-update-own']) Own</label>
+ <label>@include('settings/roles/checkbox', ['permission' => 'file-update-all']) All</label>
+ </td>
+ <td>
+ <label>@include('settings/roles/checkbox', ['permission' => 'file-delete-own']) Own</label>
+ <label>@include('settings/roles/checkbox', ['permission' => 'file-delete-all']) All</label>
+ </td>
+ </tr>
</table>
</div>
</div>
</div>
<div class="col-sm-4">
<p></p>
- <a href="{{ baseUrl("/settings/users/{$user->id}/delete") }}" class="neg button float right">Delete User</a>
+ @if($authMethod !== 'system')
+ <a href="{{ baseUrl("/settings/users/{$user->id}/delete") }}" class="neg button float right">Delete User</a>
+ @endif
</div>
</div>
<div class="row">
--- /dev/null
+@if($user->system_name == 'public')
+ <p>This user represents any guest users that visit your instance. It cannot be used for logins but is assigned automatically.</p>
+@endif
+
+<div class="form-group">
+ <label for="name">Name</label>
+ @include('form.text', ['name' => 'name'])
+</div>
+
+<div class="form-group">
+ <label for="email">Email</label>
+ @include('form.text', ['name' => 'email'])
+</div>
+
+@if(userCan('users-manage'))
+ <div class="form-group">
+ <label for="role">User Role</label>
+ @include('form/role-checkboxes', ['name' => 'roles', 'roles' => $roles])
+ </div>
+@endif
+
+<div class="form-group">
+ <a href="{{ baseUrl("/settings/users") }}" class="button muted">Cancel</a>
+ <button class="button pos" type="submit">Save</button>
+</div>
<div class="row">
<div class="col-sm-8">
<div class="compact">
- {!! $users->links() !!}
+ {{ $users->links() }}
</div>
</div>
<div class="col-sm-4">
</table>
<div>
- {!! $users->links() !!}
+ {{ $users->links() }}
</div>
</div>
--- /dev/null
+<?php
+
+if (! empty($greeting)) {
+ echo $greeting, "\n\n";
+} else {
+ echo $level == 'error' ? 'Whoops!' : 'Hello!', "\n\n";
+}
+
+if (! empty($introLines)) {
+ echo implode("\n", $introLines), "\n\n";
+}
+
+if (isset($actionText)) {
+ echo "{$actionText}: {$actionUrl}", "\n\n";
+}
+
+if (! empty($outroLines)) {
+ echo implode("\n", $outroLines), "\n\n";
+}
+
+echo 'Regards,', "\n";
+echo config('app.name'), "\n";
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+<head>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+
+ <style type="text/css" rel="stylesheet" media="all">
+ /* Media Queries */
+ @media only screen and (max-width: 500px) {
+ .button {
+ width: 100% !important;
+ }
+ }
+
+ @media only screen and (max-width: 600px) {
+ .button {
+ width: 100% !important;
+ }
+ .mobile {
+ max-width: 100%;
+ display: block;
+ width: 100%;
+ }
+ }
+ </style>
+</head>
+
+<?php
+
+$style = [
+ /* Layout ------------------------------ */
+
+ 'body' => 'margin: 0; padding: 0; width: 100%; background-color: #F2F4F6;',
+ 'email-wrapper' => 'width: 100%; margin: 0; padding: 0; background-color: #F2F4F6;',
+
+ /* Masthead ----------------------- */
+
+ 'email-masthead' => 'padding: 25px 0; text-align: center;',
+ 'email-masthead_name' => 'font-size: 24px; font-weight: 400; color: #2F3133; text-decoration: none; text-shadow: 0 1px 0 white;',
+
+ 'email-body' => 'width: 100%; margin: 0; padding: 0; border-top: 4px solid '.setting('app-color').'; border-bottom: 1px solid #EDEFF2; background-color: #FFF;',
+ 'email-body_inner' => 'width: auto; max-width: 100%; margin: 0 auto; padding: 0;',
+ 'email-body_cell' => 'padding: 35px;',
+
+ 'email-footer' => 'width: auto; max-width: 570px; margin: 0 auto; padding: 0; text-align: center;',
+ 'email-footer_cell' => 'color: #AEAEAE; padding: 35px; text-align: center;',
+
+ /* Body ------------------------------ */
+
+ 'body_action' => 'width: 100%; margin: 30px auto; padding: 0; text-align: center;',
+ 'body_sub' => 'margin-top: 25px; padding-top: 25px; border-top: 1px solid #EDEFF2;',
+
+ /* Type ------------------------------ */
+
+ 'anchor' => 'color: '.setting('app-color').';overflow-wrap: break-word;word-wrap: break-word;word-break: break-all;word-break:break-word;',
+ 'header-1' => 'margin-top: 0; color: #2F3133; font-size: 19px; font-weight: bold; text-align: left;',
+ 'paragraph' => 'margin-top: 0; color: #74787E; font-size: 16px; line-height: 1.5em;',
+ 'paragraph-sub' => 'margin-top: 0; color: #74787E; font-size: 12px; line-height: 1.5em;',
+ 'paragraph-center' => 'text-align: center;',
+
+ /* Buttons ------------------------------ */
+
+ 'button' => 'display: block; display: inline-block; width: 200px; min-height: 20px; padding: 10px;
+ background-color: #3869D4; border-radius: 3px; color: #ffffff; font-size: 15px; line-height: 25px;
+ text-align: center; text-decoration: none; -webkit-text-size-adjust: none;',
+
+ 'button--green' => 'background-color: #22BC66;',
+ 'button--red' => 'background-color: #dc4d2f;',
+ 'button--blue' => 'background-color: '.setting('app-color').';',
+];
+?>
+
+<?php $fontFamily = 'font-family: Arial, \'Helvetica Neue\', Helvetica, sans-serif;'; ?>
+
+<body style="{{ $style['body'] }}">
+ <table width="100%" cellpadding="0" cellspacing="0">
+ <tr>
+ <td align="center" class="mobile">
+ <table width="600" style="max-width: 100%; padding: 12px;text-align: left;" cellpadding="0" cellspacing="0" class="mobile">
+ <tr>
+ <td style="{{ $style['email-wrapper'] }}" align="center">
+ <table width="100%" cellpadding="0" cellspacing="0">
+ <!-- Logo -->
+ <tr>
+ <td style="{{ $style['email-masthead'] }}">
+ <a style="{{ $fontFamily }} {{ $style['email-masthead_name'] }}" href="{{ baseUrl('/') }}" target="_blank">
+ {{ setting('app-name') }}
+ </a>
+ </td>
+ </tr>
+
+ <!-- Email Body -->
+ <tr>
+ <td style="{{ $style['email-body'] }}" width="100%">
+ <table style="{{ $style['email-body_inner'] }}" align="center" width="100%" cellpadding="0" cellspacing="0">
+ <tr>
+ <td style="{{ $fontFamily }} {{ $style['email-body_cell'] }}">
+
+ <!-- Greeting -->
+ @if (!empty($greeting) || $level == 'error')
+ <h1 style="{{ $style['header-1'] }}">
+ @if (! empty($greeting))
+ {{ $greeting }}
+ @else
+ @if ($level == 'error')
+ Whoops!
+ @endif
+ @endif
+ </h1>
+ @endif
+
+ <!-- Intro -->
+ @foreach ($introLines as $line)
+ <p style="{{ $style['paragraph'] }}">
+ {{ $line }}
+ </p>
+ @endforeach
+
+ <!-- Action Button -->
+ @if (isset($actionText))
+ <table style="{{ $style['body_action'] }}" align="center" width="100%" cellpadding="0" cellspacing="0">
+ <tr>
+ <td align="center">
+ <?php
+ switch ($level) {
+ case 'success':
+ $actionColor = 'button--green';
+ break;
+ case 'error':
+ $actionColor = 'button--red';
+ break;
+ default:
+ $actionColor = 'button--blue';
+ }
+ ?>
+
+ <a href="{{ $actionUrl }}"
+ style="{{ $fontFamily }} {{ $style['button'] }} {{ $style[$actionColor] }}"
+ class="button"
+ target="_blank">
+ {{ $actionText }}
+ </a>
+ </td>
+ </tr>
+ </table>
+ @endif
+
+ <!-- Outro -->
+ @foreach ($outroLines as $line)
+ <p style="{{ $style['paragraph'] }}">
+ {{ $line }}
+ </p>
+ @endforeach
+
+
+ <!-- Sub Copy -->
+ @if (isset($actionText))
+ <table style="{{ $style['body_sub'] }}">
+ <tr>
+ <td style="{{ $fontFamily }}">
+ <p style="{{ $style['paragraph-sub'] }}">
+ If you’re having trouble clicking the "{{ $actionText }}" button,
+ copy and paste the URL below into your web browser:
+ </p>
+
+ <p style="{{ $style['paragraph-sub'] }}">
+ <a style="{{ $style['anchor'] }}" href="{{ $actionUrl }}" target="_blank">
+ {{ $actionUrl }}
+ </a>
+ </p>
+ </td>
+ </tr>
+ </table>
+ @endif
+
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+
+ <!-- Footer -->
+ <tr>
+ <td>
+ <table style="{{ $style['email-footer'] }}" align="center" width="100%" cellpadding="0" cellspacing="0">
+ <tr>
+ <td style="{{ $fontFamily }} {{ $style['email-footer_cell'] }}">
+ <p style="{{ $style['paragraph-sub'] }}">
+ © {{ date('Y') }}
+ <a style="{{ $style['anchor'] }}" href="{{ baseUrl('/') }}" target="_blank">{{ setting('app-name') }}</a>.
+ All rights reserved.
+ </p>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+</body>
+</html>
// Pages
Route::get('/{bookSlug}/page/create', 'PageController@create');
+ Route::post('/{bookSlug}/page/create/guest', 'PageController@createAsGuest');
Route::get('/{bookSlug}/draft/{pageId}', 'PageController@editDraft');
Route::post('/{bookSlug}/draft/{pageId}', 'PageController@store');
Route::get('/{bookSlug}/page/{pageSlug}', 'PageController@show');
// Revisions
Route::get('/{bookSlug}/page/{pageSlug}/revisions', 'PageController@showRevisions');
Route::get('/{bookSlug}/page/{pageSlug}/revisions/{revId}', 'PageController@showRevision');
+ Route::get('/{bookSlug}/page/{pageSlug}/revisions/{revId}/changes', 'PageController@showRevisionChanges');
Route::get('/{bookSlug}/page/{pageSlug}/revisions/{revId}/restore', 'PageController@restoreRevision');
// Chapters
Route::get('/{bookSlug}/chapter/{chapterSlug}/create-page', 'PageController@create');
+ Route::post('/{bookSlug}/chapter/{chapterSlug}/page/create/guest', 'PageController@createAsGuest');
Route::get('/{bookSlug}/chapter/create', 'ChapterController@create');
Route::post('/{bookSlug}/chapter/create', 'ChapterController@store');
Route::get('/{bookSlug}/chapter/{chapterSlug}', 'ChapterController@show');
Route::delete('/{imageId}', 'ImageController@destroy');
});
+ // File routes
+ Route::get('/files/{id}', 'FileController@get');
+ Route::post('/files/upload', 'FileController@upload');
+ Route::post('/files/upload/{id}', 'FileController@uploadUpdate');
+ Route::post('/files/link', 'FileController@attachLink');
+ Route::put('/files/{id}', 'FileController@update');
+ Route::get('/files/get/page/{pageId}', 'FileController@listForPage');
+ Route::put('/files/sort/page/{pageId}', 'FileController@sortForPage');
+ Route::delete('/files/{id}', 'FileController@delete');
+
// AJAX routes
Route::put('/ajax/page/{id}/save-draft', 'PageController@saveDraft');
Route::get('/ajax/page/{id}', 'PageController@getPageAjax');
});
-// Login using social authentication
-Route::get('/login/service/{socialDriver}', 'Auth\AuthController@getSocialLogin');
-Route::get('/login/service/{socialDriver}/callback', 'Auth\AuthController@socialCallback');
-Route::get('/login/service/{socialDriver}/detach', 'Auth\AuthController@detachSocialAccount');
+// Social auth routes
+Route::get('/login/service/{socialDriver}', 'Auth\RegisterController@getSocialLogin');
+Route::get('/login/service/{socialDriver}/callback', 'Auth\RegisterController@socialCallback');
+Route::get('/login/service/{socialDriver}/detach', 'Auth\RegisterController@detachSocialAccount');
+Route::get('/register/service/{socialDriver}', 'Auth\RegisterController@socialRegister');
// Login/Logout routes
-Route::get('/login', 'Auth\AuthController@getLogin');
-Route::post('/login', 'Auth\AuthController@postLogin');
-Route::get('/logout', 'Auth\AuthController@getLogout');
-Route::get('/register', 'Auth\AuthController@getRegister');
-Route::get('/register/confirm', 'Auth\AuthController@getRegisterConfirmation');
-Route::get('/register/confirm/awaiting', 'Auth\AuthController@showAwaitingConfirmation');
-Route::post('/register/confirm/resend', 'Auth\AuthController@resendConfirmation');
-Route::get('/register/confirm/{token}', 'Auth\AuthController@confirmEmail');
-Route::get('/register/confirm/{token}/email', 'Auth\AuthController@viewConfirmEmail');
-Route::get('/register/service/{socialDriver}', 'Auth\AuthController@socialRegister');
-Route::post('/register', 'Auth\AuthController@postRegister');
+Route::get('/login', 'Auth\LoginController@getLogin');
+Route::post('/login', 'Auth\LoginController@login');
+Route::get('/logout', 'Auth\LoginController@logout');
+Route::get('/register', 'Auth\RegisterController@getRegister');
+Route::get('/register/confirm', 'Auth\RegisterController@getRegisterConfirmation');
+Route::get('/register/confirm/awaiting', 'Auth\RegisterController@showAwaitingConfirmation');
+Route::post('/register/confirm/resend', 'Auth\RegisterController@resendConfirmation');
+Route::get('/register/confirm/{token}', 'Auth\RegisterController@confirmEmail');
+Route::post('/register', 'Auth\RegisterController@postRegister');
// Password reset link request routes...
-Route::get('/password/email', 'Auth\PasswordController@getEmail');
-Route::post('/password/email', 'Auth\PasswordController@postEmail');
+Route::get('/password/email', 'Auth\ForgotPasswordController@showLinkRequestForm');
+Route::post('/password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail');
+
// Password reset routes...
-Route::get('/password/reset/{token}', 'Auth\PasswordController@getReset');
-Route::post('/password/reset', 'Auth\PasswordController@postReset');
\ No newline at end of file
+Route::get('/password/reset/{token}', 'Auth\ResetPasswordController@showResetForm');
+Route::post('/password/reset', 'Auth\ResetPasswordController@reset');
\ No newline at end of file
--- /dev/null
+<?php
+
+class AttachmentTest extends TestCase
+{
+ /**
+ * Get a test file that can be uploaded
+ * @param $fileName
+ * @return \Illuminate\Http\UploadedFile
+ */
+ protected function getTestFile($fileName)
+ {
+ return new \Illuminate\Http\UploadedFile(base_path('tests/test-data/test-file.txt'), $fileName, 'text/plain', 55, null, true);
+ }
+
+ /**
+ * Uploads a file with the given name.
+ * @param $name
+ * @param int $uploadedTo
+ * @return string
+ */
+ protected function uploadFile($name, $uploadedTo = 0)
+ {
+ $file = $this->getTestFile($name);
+ return $this->call('POST', '/files/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.
+ */
+ protected function deleteUploads()
+ {
+ $fileService = $this->app->make(\BookStack\Services\FileService::class);
+ foreach (\BookStack\File::all() as $file) {
+ $fileService->deleteFile($file);
+ }
+ }
+
+ public function test_file_upload()
+ {
+ $page = \BookStack\Page::first();
+ $this->asAdmin();
+ $admin = $this->getAdmin();
+ $fileName = 'upload_test_file.txt';
+
+ $expectedResp = [
+ 'name' => $fileName,
+ 'uploaded_to'=> $page->id,
+ 'extension' => 'txt',
+ 'order' => 1,
+ 'created_by' => $admin->id,
+ 'updated_by' => $admin->id,
+ 'path' => $this->getUploadPath($fileName)
+ ];
+
+ $this->uploadFile($fileName, $page->id);
+ $this->assertResponseOk();
+ $this->seeJsonContains($expectedResp);
+ $this->seeInDatabase('files', $expectedResp);
+
+ $this->deleteUploads();
+ }
+
+ public function test_file_display_and_access()
+ {
+ $page = \BookStack\Page::first();
+ $this->asAdmin();
+ $admin = $this->getAdmin();
+ $fileName = 'upload_test_file.txt';
+
+ $this->uploadFile($fileName, $page->id);
+ $this->assertResponseOk();
+ $this->visit($page->getUrl())
+ ->seeLink($fileName)
+ ->click($fileName)
+ ->see('Hi, This is a test file for testing the upload process.');
+
+ $this->deleteUploads();
+ }
+
+ public function test_attaching_link_to_page()
+ {
+ $page = \BookStack\Page::first();
+ $admin = $this->getAdmin();
+ $this->asAdmin();
+
+ $this->call('POST', 'files/link', [
+ 'link' => 'https://example.com',
+ 'name' => 'Example Attachment Link',
+ 'uploaded_to' => $page->id,
+ ]);
+
+ $expectedResp = [
+ 'path' => 'https://example.com',
+ 'name' => 'Example Attachment Link',
+ 'uploaded_to' => $page->id,
+ 'created_by' => $admin->id,
+ 'updated_by' => $admin->id,
+ 'external' => true,
+ 'order' => 1,
+ 'extension' => ''
+ ];
+
+ $this->assertResponseOk();
+ $this->seeJsonContains($expectedResp);
+ $this->seeInDatabase('files', $expectedResp);
+
+ $this->visit($page->getUrl())->seeLink('Example Attachment Link')
+ ->click('Example Attachment Link')->seePageIs('https://example.com');
+
+ $this->deleteUploads();
+ }
+
+ public function test_attachment_updating()
+ {
+ $page = \BookStack\Page::first();
+ $this->asAdmin();
+
+ $this->call('POST', 'files/link', [
+ 'link' => 'https://example.com',
+ 'name' => 'Example Attachment Link',
+ 'uploaded_to' => $page->id,
+ ]);
+
+ $attachmentId = \BookStack\File::first()->id;
+
+ $this->call('PUT', 'files/' . $attachmentId, [
+ 'uploaded_to' => $page->id,
+ 'name' => 'My new attachment name',
+ 'link' => 'https://test.example.com'
+ ]);
+
+ $expectedResp = [
+ 'path' => 'https://test.example.com',
+ 'name' => 'My new attachment name',
+ 'uploaded_to' => $page->id
+ ];
+
+ $this->assertResponseOk();
+ $this->seeJsonContains($expectedResp);
+ $this->seeInDatabase('files', $expectedResp);
+
+ $this->deleteUploads();
+ }
+
+ public function test_file_deletion()
+ {
+ $page = \BookStack\Page::first();
+ $this->asAdmin();
+ $fileName = 'deletion_test.txt';
+ $this->uploadFile($fileName, $page->id);
+
+ $filePath = base_path('storage/' . $this->getUploadPath($fileName));
+
+ $this->assertTrue(file_exists($filePath), 'File at path ' . $filePath . ' does not exist');
+
+ $attachmentId = \BookStack\File::first()->id;
+ $this->call('DELETE', 'files/' . $attachmentId);
+
+ $this->dontSeeInDatabase('files', [
+ 'name' => $fileName
+ ]);
+ $this->assertFalse(file_exists($filePath), 'File at path ' . $filePath . ' was not deleted as expected');
+
+ $this->deleteUploads();
+ }
+
+ public function test_attachment_deletion_on_page_deletion()
+ {
+ $page = \BookStack\Page::first();
+ $this->asAdmin();
+ $fileName = 'deletion_test.txt';
+ $this->uploadFile($fileName, $page->id);
+
+ $filePath = base_path('storage/' . $this->getUploadPath($fileName));
+
+ $this->assertTrue(file_exists($filePath), 'File at path ' . $filePath . ' does not exist');
+ $this->seeInDatabase('files', [
+ 'name' => $fileName
+ ]);
+
+ $this->call('DELETE', $page->getUrl());
+
+ $this->dontSeeInDatabase('files', [
+ 'name' => $fileName
+ ]);
+ $this->assertFalse(file_exists($filePath), 'File at path ' . $filePath . ' was not deleted as expected');
+
+ $this->deleteUploads();
+ }
+}
<?php
-use BookStack\EmailConfirmation;
+use BookStack\Notifications\ConfirmEmail;
+use Illuminate\Support\Facades\Notification;
class AuthTest extends TestCase
{
public function test_confirmed_registration()
{
+ // Fake notifications
+ Notification::fake();
+
// Set settings and get user instance
$this->setSettings(['registration-enabled' => 'true', 'registration-confirmation' => 'true']);
$user = factory(\BookStack\User::class)->make();
- // Mock Mailer to ensure mail is being sent
- $mockMailer = Mockery::mock('Illuminate\Contracts\Mail\Mailer');
- $mockMailer->shouldReceive('send')->with('emails/email-confirmation', Mockery::type('array'), Mockery::type('callable'))->twice();
- $this->app->instance('mailer', $mockMailer);
-
// Go through registration process
$this->visit('/register')
->see('Sign Up')
->seePageIs('/register/confirm')
->seeInDatabase('users', ['name' => $user->name, 'email' => $user->email, 'email_confirmed' => false]);
+ // Ensure notification sent
+ $dbUser = \BookStack\User::where('email', '=', $user->email)->first();
+ Notification::assertSentTo($dbUser, ConfirmEmail::class);
+
// Test access and resend confirmation email
$this->login($user->email, $user->password)
->seePageIs('/register/confirm/awaiting')
->seePageIs('/register/confirm/awaiting')
->press('Resend Confirmation Email');
- // Get confirmation
- $user = $user->where('email', '=', $user->email)->first();
- $emailConfirmation = EmailConfirmation::where('user_id', '=', $user->id)->first();
-
-
- // Check confirmation email button and confirmation activation.
- $this->visit('/register/confirm/' . $emailConfirmation->token . '/email')
- ->see('Email Confirmation')
- ->click('Confirm Email')
+ // Get confirmation and confirm notification matches
+ $emailConfirmation = DB::table('email_confirmations')->where('user_id', '=', $dbUser->id)->first();
+ Notification::assertSentTo($dbUser, ConfirmEmail::class, function($notification, $channels) use ($emailConfirmation) {
+ return $notification->token === $emailConfirmation->token;
+ });
+
+ // Check confirmation email confirmation activation.
+ $this->visit('/register/confirm/' . $emailConfirmation->token)
->seePageIs('/')
->see($user->name)
->notSeeInDatabase('email_confirmations', ['token' => $emailConfirmation->token])
- ->seeInDatabase('users', ['name' => $user->name, 'email' => $user->email, 'email_confirmed' => true]);
+ ->seeInDatabase('users', ['name' => $dbUser->name, 'email' => $dbUser->email, 'email_confirmed' => true]);
}
public function test_restricted_registration()
public function test_user_updating()
{
- $user = \BookStack\User::all()->last();
+ $user = $this->getNormalUser();
$password = $user->password;
$this->asAdmin()
->visit('/settings/users')
public function test_user_password_update()
{
- $user = \BookStack\User::all()->last();
+ $user = $this->getNormalUser();
$userProfilePage = '/settings/users/' . $user->id;
$this->asAdmin()
->visit($userProfilePage)
public function test_user_edit_form()
{
- $editUser = User::all()->last();
+ $editUser = $this->getNormalUser();
$this->asAdmin()->visit('/settings/users/' . $editUser->id)
->see('Edit User')
->dontSee('Password')
public function test_non_admins_cannot_change_auth_id()
{
- $testUser = User::all()->last();
+ $testUser = $this->getNormalUser();
$this->actingAs($testUser)->visit('/settings/users/' . $testUser->id)
->dontSee('External Authentication');
}
->type('super test page', '#name')
->press('Save Page')
// Check redirect
- ->seePageIs($newPageUrl)
- ->visit($pageUrl)
+ ->seePageIs($newPageUrl);
+
+ $this->visit($pageUrl)
->seePageIs($newPageUrl);
}
->visit($chapter->getUrl() . '/create-page')
->visit($book->getUrl())
->seeInElement('.page-list', 'New Page');
-
+
$this->asAdmin()
->visit($book->getUrl())
->dontSeeInElement('.page-list', 'New Page')
*/
protected function getTestImage($fileName)
{
- return new \Illuminate\Http\UploadedFile(base_path('tests/test-image.jpg'), $fileName, 'image/jpeg', 5238);
+ return new \Illuminate\Http\UploadedFile(base_path('tests/test-data/test-image.jpg'), $fileName, 'image/jpeg', 5238);
}
/**
$this->deleteImage($relPath);
$this->seeInDatabase('images', [
- 'url' => url($relPath),
+ 'url' => $this->baseUrl . $relPath,
'type' => 'gallery',
'uploaded_to' => $page->id,
'path' => $relPath,
$this->assertResponseOk();
$this->dontSeeInDatabase('images', [
- 'url' => $relPath,
+ 'url' => $this->baseUrl . $relPath,
'type' => 'gallery'
]);
->dontSeeInElement('.book-content', $otherPage->name);
}
- public function test_public_role_not_visible_in_user_edit_screen()
+ public function test_public_role_visible_in_user_edit_screen()
{
$user = \BookStack\User::first();
$this->asAdmin()->visit('/settings/users/' . $user->id)
->seeElement('#roles-admin')
- ->dontSeeElement('#roles-public');
+ ->seeElement('#roles-public');
}
- public function test_public_role_not_visible_in_role_listing()
+ public function test_public_role_visible_in_role_listing()
{
$this->asAdmin()->visit('/settings/roles')
->see('Admin')
- ->dontSee('Public');
+ ->see('Public');
}
- public function test_public_role_not_visible_in_default_role_setting()
+ public function test_public_role_visible_in_default_role_setting()
{
$this->asAdmin()->visit('/settings')
->seeElement('[data-role-name="admin"]')
- ->dontSeeElement('[data-role-name="public"]');
+ ->seeElement('[data-role-name="public"]');
}
+ public function test_public_role_not_deleteable()
+ {
+ $this->asAdmin()->visit('/settings/roles')
+ ->click('Public')
+ ->see('Edit Role')
+ ->click('Delete Role')
+ ->press('Confirm')
+ ->see('Delete Role')
+ ->see('Cannot be deleted');
+ }
+
}
--- /dev/null
+<?php
+
+class PublicActionTest extends TestCase
+{
+
+ public function test_app_not_public()
+ {
+ $this->setSettings(['app-public' => 'false']);
+ $book = \BookStack\Book::orderBy('name', 'asc')->first();
+ $this->visit('/books')->seePageIs('/login');
+ $this->visit($book->getUrl())->seePageIs('/login');
+
+ $page = \BookStack\Page::first();
+ $this->visit($page->getUrl())->seePageIs('/login');
+ }
+
+ public function test_books_viewable()
+ {
+ $this->setSettings(['app-public' => 'true']);
+ $books = \BookStack\Book::orderBy('name', 'asc')->take(10)->get();
+ $bookToVisit = $books[1];
+
+ // Check books index page is showing
+ $this->visit('/books')
+ ->seeStatusCode(200)
+ ->see($books[0]->name)
+ // Check individual book page is showing and it's child contents are visible.
+ ->click($bookToVisit->name)
+ ->seePageIs($bookToVisit->getUrl())
+ ->see($bookToVisit->name)
+ ->see($bookToVisit->chapters()->first()->name);
+ }
+
+ public function test_chapters_viewable()
+ {
+ $this->setSettings(['app-public' => 'true']);
+ $chapterToVisit = \BookStack\Chapter::first();
+ $pageToVisit = $chapterToVisit->pages()->first();
+
+ // Check chapters index page is showing
+ $this->visit($chapterToVisit->getUrl())
+ ->seeStatusCode(200)
+ ->see($chapterToVisit->name)
+ // Check individual chapter page is showing and it's child contents are visible.
+ ->see($pageToVisit->name)
+ ->click($pageToVisit->name)
+ ->see($chapterToVisit->book->name)
+ ->see($chapterToVisit->name)
+ ->seePageIs($pageToVisit->getUrl());
+ }
+
+ public function test_public_page_creation()
+ {
+ $this->setSettings(['app-public' => 'true']);
+ $publicRole = \BookStack\Role::getSystemRole('public');
+ // Grant all permissions to public
+ $publicRole->permissions()->detach();
+ foreach (\BookStack\RolePermission::all() as $perm) {
+ $publicRole->attachPermission($perm);
+ }
+ $this->app[\BookStack\Services\PermissionService::class]->buildJointPermissionForRole($publicRole);
+
+ $chapter = \BookStack\Chapter::first();
+ $this->visit($chapter->book->getUrl());
+ $this->visit($chapter->getUrl())
+ ->click('New Page')
+ ->see('Create Page')
+ ->seePageIs($chapter->getUrl('/create-page'));
+
+ $this->submitForm('Continue', [
+ 'name' => 'My guest page'
+ ])->seePageIs($chapter->book->getUrl('/page/my-guest-page/edit'));
+
+ $user = \BookStack\User::getDefault();
+ $this->seeInDatabase('pages', [
+ 'name' => 'My guest page',
+ 'chapter_id' => $chapter->id,
+ 'created_by' => $user->id,
+ 'updated_by' => $user->id
+ ]);
+ }
+
+}
\ No newline at end of file
+++ /dev/null
-<?php
-
-class PublicViewTest extends TestCase
-{
-
- public function test_books_viewable()
- {
- $this->setSettings(['app-public' => 'true']);
- $books = \BookStack\Book::orderBy('name', 'asc')->take(10)->get();
- $bookToVisit = $books[1];
-
- // Check books index page is showing
- $this->visit('/books')
- ->seeStatusCode(200)
- ->see($books[0]->name)
- // Check individual book page is showing and it's child contents are visible.
- ->click($bookToVisit->name)
- ->seePageIs($bookToVisit->getUrl())
- ->see($bookToVisit->name)
- ->see($bookToVisit->chapters()->first()->name);
- }
-
- public function test_chapters_viewable()
- {
- $this->setSettings(['app-public' => 'true']);
- $chapterToVisit = \BookStack\Chapter::first();
- $pageToVisit = $chapterToVisit->pages()->first();
-
- // Check chapters index page is showing
- $this->visit($chapterToVisit->getUrl())
- ->seeStatusCode(200)
- ->see($chapterToVisit->name)
- // Check individual chapter page is showing and it's child contents are visible.
- ->see($pageToVisit->name)
- ->click($pageToVisit->name)
- ->see($chapterToVisit->book->name)
- ->see($chapterToVisit->name)
- ->seePageIs($pageToVisit->getUrl());
- }
-
-}
\ No newline at end of file
return $this->actingAs($this->editor);
}
+ /**
+ * Get a user that's not a system user such as the guest user.
+ */
+ public function getNormalUser()
+ {
+ return \BookStack\User::where('system_name', '=', null)->get()->last();
+ }
+
/**
* Quickly sets an array of settings.
* @param $settingsArray
->seePageIs('/user/' . $newUser->id)
->see($newUser->name);
}
+
+ public function test_guest_profile_shows_limited_form()
+ {
+ $this->asAdmin()
+ ->visit('/settings/users')
+ ->click('Guest')
+ ->dontSeeElement('#password');
+ }
+
+ public function test_guest_profile_cannot_be_deleted()
+ {
+ $guestUser = \BookStack\User::getDefault();
+ $this->asAdmin()->visit('/settings/users/' . $guestUser->id . '/delete')
+ ->see('Delete User')->see('Guest')
+ ->press('Confirm')
+ ->seePageIs('/settings/users/' . $guestUser->id)
+ ->see('cannot delete the guest user');
+ }
}
--- /dev/null
+Hi, This is a test file for testing the upload process.
\ No newline at end of file