diff --git a/.env.example b/.env.example index d3cf77234..91e59f966 100644 --- a/.env.example +++ b/.env.example @@ -14,14 +14,28 @@ CACHE_DRIVER=file SESSION_DRIVER=file QUEUE_DRIVER=sync +# Storage +STORAGE_TYPE=local +# Amazon S3 Config +STORAGE_S3_KEY=false +STORAGE_S3_SECRET=false +STORAGE_S3_REGION=false +STORAGE_S3_BUCKET=false +# Storage URL +# Used to prefix image urls for when using custom domains/cdns +STORAGE_URL=false + # Social Authentication information. Defaults as off. GITHUB_APP_ID=false GITHUB_APP_SECRET=false GOOGLE_APP_ID=false GOOGLE_APP_SECRET=false -# URL for social login redirects, NO TRAILING SLASH +# URL used for social login redirects, NO TRAILING SLASH APP_URL=http://bookstack.dev +# External services +USE_GRAVATAR=true + # Mail settings MAIL_DRIVER=smtp MAIL_HOST=localhost diff --git a/app/Entity.php b/app/Entity.php index 977b02e77..5ccc016a3 100644 --- a/app/Entity.php +++ b/app/Entity.php @@ -7,23 +7,7 @@ use Illuminate\Database\Eloquent\Model; abstract class Entity extends Model { - /** - * Relation for the user that created this entity. - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function createdBy() - { - return $this->belongsTo('BookStack\User', 'created_by'); - } - - /** - * Relation for the user that updated this entity. - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function updatedBy() - { - return $this->belongsTo('BookStack\User', 'updated_by'); - } + use Ownable; /** * Compares this entity to another given entity. @@ -97,18 +81,29 @@ abstract class Entity extends Model */ public static function isA($type) { - return static::getName() === strtolower($type); + return static::getClassName() === strtolower($type); } /** * Gets the class name. * @return string */ - public static function getName() + public static function getClassName() { return strtolower(array_slice(explode('\\', static::class), -1, 1)[0]); } + /** + *Gets a limited-length version of the entities name. + * @param int $length + * @return string + */ + public function getShortName($length = 25) + { + if(strlen($this->name) <= $length) return $this->name; + return substr($this->name, 0, $length-3) . '...'; + } + /** * Perform a full-text search on this entity. * @param string[] $fieldsToSearch @@ -123,20 +118,20 @@ abstract class Entity extends Model $termString .= $term . '* '; } $fields = implode(',', $fieldsToSearch); - $search = static::whereRaw('MATCH(' . $fields . ') AGAINST(? IN BOOLEAN MODE)', [$termString]); + $termStringEscaped = \DB::connection()->getPdo()->quote($termString); + $search = static::addSelect(\DB::raw('*, MATCH(name) AGAINST('.$termStringEscaped.' IN BOOLEAN MODE) AS title_relevance')); + $search = $search->whereRaw('MATCH(' . $fields . ') AGAINST(? IN BOOLEAN MODE)', [$termString]); + + // Add additional where terms foreach ($wheres as $whereTerm) { $search->where($whereTerm[0], $whereTerm[1], $whereTerm[2]); } - if (!static::isA('book')) { - $search = $search->with('book'); - } + // Load in relations + if (!static::isA('book')) $search = $search->with('book'); + if (static::isA('page')) $search = $search->with('chapter'); - if (static::isA('page')) { - $search = $search->with('chapter'); - } - - return $search->get(); + return $search->orderBy('title_relevance', 'desc')->get(); } /** diff --git a/app/Http/Controllers/BookController.php b/app/Http/Controllers/BookController.php index 6b2d6928d..a4365d605 100644 --- a/app/Http/Controllers/BookController.php +++ b/app/Http/Controllers/BookController.php @@ -42,8 +42,10 @@ class BookController extends Controller public function index() { $books = $this->bookRepo->getAllPaginated(10); - $recents = $this->signedIn ? $this->bookRepo->getRecentlyViewed(10, 0) : false; - return view('books/index', ['books' => $books, 'recents' => $recents]); + $recents = $this->signedIn ? $this->bookRepo->getRecentlyViewed(4, 0) : false; + $popular = $this->bookRepo->getPopular(4, 0); + $this->setPageTitle('Books'); + return view('books/index', ['books' => $books, 'recents' => $recents, 'popular' => $popular]); } /** @@ -54,6 +56,7 @@ class BookController extends Controller public function create() { $this->checkPermission('book-create'); + $this->setPageTitle('Create New Book'); return view('books/create'); } @@ -88,8 +91,9 @@ class BookController extends Controller public function show($slug) { $book = $this->bookRepo->getBySlug($slug); - Views::add($book); $bookChildren = $this->bookRepo->getChildren($book); + Views::add($book); + $this->setPageTitle($book->getShortName()); return view('books/show', ['book' => $book, 'current' => $book, 'bookChildren' => $bookChildren]); } @@ -103,6 +107,7 @@ class BookController extends Controller { $this->checkPermission('book-update'); $book = $this->bookRepo->getBySlug($slug); + $this->setPageTitle('Edit Book ' . $book->getShortName()); return view('books/edit', ['book' => $book, 'current' => $book]); } @@ -138,6 +143,7 @@ class BookController extends Controller { $this->checkPermission('book-delete'); $book = $this->bookRepo->getBySlug($bookSlug); + $this->setPageTitle('Delete Book ' . $book->getShortName()); return view('books/delete', ['book' => $book, 'current' => $book]); } @@ -152,9 +158,16 @@ class BookController extends Controller $book = $this->bookRepo->getBySlug($bookSlug); $bookChildren = $this->bookRepo->getChildren($book); $books = $this->bookRepo->getAll(); + $this->setPageTitle('Sort Book ' . $book->getShortName()); return view('books/sort', ['book' => $book, 'current' => $book, 'books' => $books, 'bookChildren' => $bookChildren]); } + /** + * Shows the sort box for a single book. + * Used via AJAX when loading in extra books to a sort. + * @param $bookSlug + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ public function getSortItem($bookSlug) { $book = $this->bookRepo->getBySlug($bookSlug); diff --git a/app/Http/Controllers/ChapterController.php b/app/Http/Controllers/ChapterController.php index 1fe1e8b3e..fc13e8b58 100644 --- a/app/Http/Controllers/ChapterController.php +++ b/app/Http/Controllers/ChapterController.php @@ -40,6 +40,7 @@ class ChapterController extends Controller { $this->checkPermission('chapter-create'); $book = $this->bookRepo->getBySlug($bookSlug); + $this->setPageTitle('Create New Chapter'); return view('chapters/create', ['book' => $book, 'current' => $book]); } @@ -79,6 +80,7 @@ class ChapterController extends Controller $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id); $sidebarTree = $this->bookRepo->getChildren($book); Views::add($chapter); + $this->setPageTitle($chapter->getShortName()); return view('chapters/show', ['book' => $book, 'chapter' => $chapter, 'current' => $chapter, 'sidebarTree' => $sidebarTree]); } @@ -93,6 +95,7 @@ class ChapterController extends Controller $this->checkPermission('chapter-update'); $book = $this->bookRepo->getBySlug($bookSlug); $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id); + $this->setPageTitle('Edit Chapter' . $chapter->getShortName()); return view('chapters/edit', ['book' => $book, 'chapter' => $chapter, 'current' => $chapter]); } @@ -127,6 +130,7 @@ class ChapterController extends Controller $this->checkPermission('chapter-delete'); $book = $this->bookRepo->getBySlug($bookSlug); $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id); + $this->setPageTitle('Delete Chapter' . $chapter->getShortName()); return view('chapters/delete', ['book' => $book, 'chapter' => $chapter, 'current' => $chapter]); } diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index 4cc865d29..5dc79eb02 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -42,6 +42,15 @@ abstract class Controller extends BaseController $this->signedIn = auth()->check(); } + /** + * Adds the page title into the view. + * @param $title + */ + public function setPageTitle($title) + { + view()->share('pageTitle', $title); + } + /** * Checks for a permission. * diff --git a/app/Http/Controllers/ImageController.php b/app/Http/Controllers/ImageController.php index fd7901570..146dd0c05 100644 --- a/app/Http/Controllers/ImageController.php +++ b/app/Http/Controllers/ImageController.php @@ -2,6 +2,7 @@ namespace BookStack\Http\Controllers; +use BookStack\Repos\ImageRepo; use Illuminate\Filesystem\Filesystem as File; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; @@ -14,125 +15,78 @@ class ImageController extends Controller { protected $image; protected $file; + protected $imageRepo; /** * ImageController constructor. - * @param Image $image - * @param File $file + * @param Image $image + * @param File $file + * @param ImageRepo $imageRepo */ - public function __construct(Image $image, File $file) + public function __construct(Image $image, File $file, ImageRepo $imageRepo) { $this->image = $image; $this->file = $file; + $this->imageRepo = $imageRepo; parent::__construct(); } /** - * Get all images, Paginated + * Get all images for a specific type, Paginated * @param int $page * @return \Illuminate\Http\JsonResponse */ - public function getAll($page = 0) + public function getAllByType($type, $page = 0) { - $pageSize = 30; - $images = $this->image->orderBy('created_at', 'desc') - ->skip($page * $pageSize)->take($pageSize)->get(); - foreach ($images as $image) { - $this->loadSizes($image); - } - $hasMore = $this->image->orderBy('created_at', 'desc') - ->skip(($page + 1) * $pageSize)->take($pageSize)->count() > 0; - return response()->json([ - 'images' => $images, - 'hasMore' => $hasMore - ]); + $imgData = $this->imageRepo->getPaginatedByType($type, $page); + return response()->json($imgData); } /** - * Loads the standard thumbnail sizes for an image. - * @param Image $image + * Get all images for a user. + * @param int $page + * @return \Illuminate\Http\JsonResponse */ - private function loadSizes(Image $image) + public function getAllForUserType($page = 0) { - $image->thumbnail = $this->getThumbnail($image, 150, 150); - $image->display = $this->getThumbnail($image, 840, 0, true); + $imgData = $this->imageRepo->getPaginatedByType('user', $page, 24, $this->currentUser->id); + return response()->json($imgData); } - /** - * Get the thumbnail for an image. - * If $keepRatio is true only the width will be used. - * @param $image - * @param int $width - * @param int $height - * @param bool $keepRatio - * @return string - */ - public function getThumbnail($image, $width = 220, $height = 220, $keepRatio = false) - { - $explodedPath = explode('/', $image->url); - $dirPrefix = $keepRatio ? 'scaled-' : 'thumbs-'; - array_splice($explodedPath, 4, 0, [$dirPrefix . $width . '-' . $height]); - $thumbPath = implode('/', $explodedPath); - $thumbFilePath = public_path() . $thumbPath; - - // Return the thumbnail url path if already exists - if (file_exists($thumbFilePath)) { - return $thumbPath; - } - - // Otherwise create the thumbnail - $thumb = ImageTool::make(public_path() . $image->url); - if($keepRatio) { - $thumb->resize($width, null, function ($constraint) { - $constraint->aspectRatio(); - $constraint->upsize(); - }); - } else { - $thumb->fit($width, $height); - } - - // Create thumbnail folder if it does not exist - if (!file_exists(dirname($thumbFilePath))) { - mkdir(dirname($thumbFilePath), 0775, true); - } - - //Save Thumbnail - $thumb->save($thumbFilePath); - return $thumbPath; - } /** * Handles image uploads for use on pages. + * @param string $type * @param Request $request * @return \Illuminate\Http\JsonResponse */ - public function upload(Request $request) + public function uploadByType($type, Request $request) { $this->checkPermission('image-create'); $this->validate($request, [ 'file' => 'image|mimes:jpeg,gif,png' ]); - $imageUpload = $request->file('file'); - $name = str_replace(' ', '-', $imageUpload->getClientOriginalName()); - $storageName = substr(sha1(time()), 0, 10) . '-' . $name; - $imagePath = '/uploads/images/' . Date('Y-m-M') . '/'; - $storagePath = public_path() . $imagePath; - $fullPath = $storagePath . $storageName; - while (file_exists($fullPath)) { - $storageName = substr(sha1(rand()), 0, 3) . $storageName; - $fullPath = $storagePath . $storageName; - } - $imageUpload->move($storagePath, $storageName); - // Create and save image object - $this->image->name = $name; - $this->image->url = $imagePath . $storageName; - $this->image->created_by = auth()->user()->id; - $this->image->updated_by = auth()->user()->id; - $this->image->save(); - $this->loadSizes($this->image); - return response()->json($this->image); + $imageUpload = $request->file('file'); + $image = $this->imageRepo->saveNew($imageUpload, $type); + return response()->json($image); + } + + /** + * Generate a sized thumbnail for an image. + * @param $id + * @param $width + * @param $height + * @param $crop + * @return \Illuminate\Http\JsonResponse + */ + public function getThumbnail($id, $width, $height, $crop) + { + $this->checkPermission('image-create'); + $image = $this->imageRepo->getById($id); + $thumbnailUrl = $this->imageRepo->getThumbnail($image, $width, $height, $crop == 'false'); + return response()->json(['url' => $thumbnailUrl]); } /** @@ -147,13 +101,12 @@ class ImageController extends Controller $this->validate($request, [ 'name' => 'required|min:2|string' ]); - $image = $this->image->findOrFail($imageId); - $image->fill($request->all()); - $image->save(); - $this->loadSizes($image); - return response()->json($this->image); + $image = $this->imageRepo->getById($imageId); + $image = $this->imageRepo->updateImageDetails($image, $request->all()); + return response()->json($image); } + /** * Deletes an image and all thumbnail/image files * @param PageRepo $pageRepo @@ -164,41 +117,18 @@ class ImageController extends Controller public function destroy(PageRepo $pageRepo, Request $request, $id) { $this->checkPermission('image-delete'); - $image = $this->image->findOrFail($id); + $image = $this->imageRepo->getById($id); // Check if this image is used on any pages - $pageSearch = $pageRepo->searchForImage($image->url); $isForced = ($request->has('force') && ($request->get('force') === 'true') || $request->get('force') === true); - if ($pageSearch !== false && !$isForced) { - return response()->json($pageSearch, 400); - } - - // Delete files - $folder = public_path() . dirname($image->url); - $fileName = basename($image->url); - - // Delete thumbnails - foreach (glob($folder . '/*') as $file) { - if (is_dir($file)) { - $thumbName = $file . '/' . $fileName; - if (file_exists($file)) { - unlink($thumbName); - } - // Remove thumb folder if empty - if (count(glob($file . '/*')) === 0) { - rmdir($file); - } + if (!$isForced) { + $pageSearch = $pageRepo->searchForImage($image->url); + if ($pageSearch !== false) { + return response()->json($pageSearch, 400); } } - // Delete file and database entry - unlink($folder . '/' . $fileName); - $image->delete(); - - // Delete parent folder if empty - if (count(glob($folder . '/*')) === 0) { - rmdir($folder); - } + $this->imageRepo->destroyImage($image); return response()->json('Image Deleted'); } diff --git a/app/Http/Controllers/PageController.php b/app/Http/Controllers/PageController.php index d9d53178e..2002fbdf0 100644 --- a/app/Http/Controllers/PageController.php +++ b/app/Http/Controllers/PageController.php @@ -46,6 +46,7 @@ class PageController extends Controller $this->checkPermission('page-create'); $book = $this->bookRepo->getBySlug($bookSlug); $chapter = $chapterSlug ? $this->chapterRepo->getBySlug($chapterSlug, $book->id) : false; + $this->setPageTitle('Create New Page'); return view('pages/create', ['book' => $book, 'chapter' => $chapter]); } @@ -89,6 +90,7 @@ class PageController extends Controller $page = $this->pageRepo->getBySlug($pageSlug, $book->id); $sidebarTree = $this->bookRepo->getChildren($book); Views::add($page); + $this->setPageTitle($page->getShortName()); return view('pages/show', ['page' => $page, 'book' => $book, 'current' => $page, 'sidebarTree' => $sidebarTree]); } @@ -104,6 +106,7 @@ class PageController extends Controller $this->checkPermission('page-update'); $book = $this->bookRepo->getBySlug($bookSlug); $page = $this->pageRepo->getBySlug($pageSlug, $book->id); + $this->setPageTitle('Editing Page ' . $page->getShortName()); return view('pages/edit', ['page' => $page, 'book' => $book, 'current' => $page]); } @@ -148,6 +151,7 @@ class PageController extends Controller $this->checkPermission('page-delete'); $book = $this->bookRepo->getBySlug($bookSlug); $page = $this->pageRepo->getBySlug($pageSlug, $book->id); + $this->setPageTitle('Delete Page ' . $page->getShortName()); return view('pages/delete', ['book' => $book, 'page' => $page, 'current' => $page]); } @@ -179,6 +183,7 @@ class PageController extends Controller { $book = $this->bookRepo->getBySlug($bookSlug); $page = $this->pageRepo->getBySlug($pageSlug, $book->id); + $this->setPageTitle('Revisions For ' . $page->getShortName()); return view('pages/revisions', ['page' => $page, 'book' => $book, 'current' => $page]); } @@ -195,6 +200,7 @@ class PageController extends Controller $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]); } diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php index c6222156d..c9ca1f09f 100644 --- a/app/Http/Controllers/SearchController.php +++ b/app/Http/Controllers/SearchController.php @@ -45,6 +45,7 @@ class SearchController extends Controller $pages = $this->pageRepo->getBySearch($searchTerm); $books = $this->bookRepo->getBySearch($searchTerm); $chapters = $this->chapterRepo->getBySearch($searchTerm); + $this->setPageTitle('Search For ' . $searchTerm); return view('search/all', ['pages' => $pages, 'books' => $books, 'chapters' => $chapters, 'searchTerm' => $searchTerm]); } diff --git a/app/Http/Controllers/SettingController.php b/app/Http/Controllers/SettingController.php index 3e96de62e..bca48807f 100644 --- a/app/Http/Controllers/SettingController.php +++ b/app/Http/Controllers/SettingController.php @@ -18,6 +18,7 @@ class SettingController extends Controller public function index() { $this->checkPermission('settings-update'); + $this->setPageTitle('Settings'); return view('settings/index'); } diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index dacb91a7b..3f41b2d0e 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -4,7 +4,7 @@ namespace BookStack\Http\Controllers; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Hash; +use Illuminate\Http\Response; use BookStack\Http\Requests; use BookStack\Repos\UserRepo; use BookStack\Services\SocialAuthService; @@ -18,7 +18,8 @@ class UserController extends Controller /** * UserController constructor. - * @param $user + * @param User $user + * @param UserRepo $userRepo */ public function __construct(User $user, UserRepo $userRepo) { @@ -29,18 +30,17 @@ class UserController extends Controller /** * Display a listing of the users. - * * @return Response */ public function index() { $users = $this->user->all(); + $this->setPageTitle('Users'); return view('users/index', ['users' => $users]); } /** * Show the form for creating a new user. - * * @return Response */ public function create() @@ -51,7 +51,6 @@ class UserController extends Controller /** * Store a newly created user in storage. - * * @param Request $request * @return Response */ @@ -60,7 +59,7 @@ class UserController extends Controller $this->checkPermission('user-create'); $this->validate($request, [ 'name' => 'required', - 'email' => 'required|email', + 'email' => 'required|email|unique:users,email', 'password' => 'required|min:5', 'password-confirm' => 'required|same:password', 'role' => 'required|exists:roles,id' @@ -71,13 +70,20 @@ class UserController extends Controller $user->save(); $user->attachRoleId($request->get('role')); + + // Get avatar from gravatar and save + if (!env('DISABLE_EXTERNAL_SERVICES', false)) { + $avatar = \Images::saveUserGravatar($user); + $user->avatar()->associate($avatar); + $user->save(); + } + return redirect('/users'); } /** * Show the form for editing the specified user. - * * @param int $id * @param SocialAuthService $socialAuthService * @return Response @@ -90,12 +96,12 @@ class UserController extends Controller $user = $this->user->findOrFail($id); $activeSocialDrivers = $socialAuthService->getActiveDrivers(); + $this->setPageTitle('User Profile'); return view('users/edit', ['user' => $user, 'activeSocialDrivers' => $activeSocialDrivers]); } /** * Update the specified user in storage. - * * @param Request $request * @param int $id * @return Response @@ -139,12 +145,12 @@ class UserController extends Controller return $this->currentUser->id == $id; }); $user = $this->user->findOrFail($id); + $this->setPageTitle('Delete User ' . $user->name); return view('users/delete', ['user' => $user]); } /** * Remove the specified user from storage. - * * @param int $id * @return Response */ @@ -153,14 +159,14 @@ class UserController extends Controller $this->checkPermissionOr('user-delete', function () use ($id) { return $this->currentUser->id == $id; }); + $user = $this->userRepo->getById($id); - // Delete social accounts - if($this->userRepo->isOnlyAdmin($user)) { + if ($this->userRepo->isOnlyAdmin($user)) { session()->flash('error', 'You cannot delete the only admin'); return redirect($user->getEditUrl()); } - $user->socialAccounts()->delete(); - $user->delete(); + $this->userRepo->destroy($user); + return redirect('/users'); } } diff --git a/app/Http/routes.php b/app/Http/routes.php index e605dbee1..23d4c33ab 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -45,8 +45,6 @@ Route::group(['middleware' => 'auth'], function () { }); - // Uploads - Route::post('/upload/image', 'ImageController@upload'); // Users Route::get('/users', 'UserController@index'); @@ -58,10 +56,18 @@ Route::group(['middleware' => 'auth'], function () { Route::delete('/users/{id}', 'UserController@destroy'); // Image routes - Route::get('/images/all', 'ImageController@getAll'); - Route::put('/images/update/{imageId}', 'ImageController@update'); - Route::delete('/images/{imageId}', 'ImageController@destroy'); - Route::get('/images/all/{page}', 'ImageController@getAll'); + Route::group(['prefix' => 'images'], function() { + // Get for user images + Route::get('/user/all', 'ImageController@getAllForUserType'); + Route::get('/user/all/{page}', 'ImageController@getAllForUserType'); + // Standard get, update and deletion for all types + Route::get('/thumb/{id}/{width}/{height}/{crop}', 'ImageController@getThumbnail'); + Route::put('/update/{imageId}', 'ImageController@update'); + Route::post('/{type}/upload', 'ImageController@uploadByType'); + Route::get('/{type}/all', 'ImageController@getAllByType'); + Route::get('/{type}/all/{page}', 'ImageController@getAllByType'); + Route::delete('/{imageId}', 'ImageController@destroy'); + }); // Links Route::get('/link/{id}', 'PageController@redirectFromLink'); diff --git a/app/Image.php b/app/Image.php index 7c77440f9..3ac084d8f 100644 --- a/app/Image.php +++ b/app/Image.php @@ -3,22 +3,24 @@ namespace BookStack; -class Image extends Entity +use Illuminate\Database\Eloquent\Model; +use Images; + +class Image extends Model { + use Ownable; protected $fillable = ['name']; - public function getFilePath() - { - return storage_path() . $this->url; - } - /** - * Get the url for this item. + * Get a thumbnail for this image. + * @param int $width + * @param int $height + * @param bool|false $keepRatio * @return string */ - public function getUrl() + public function getThumb($width, $height, $keepRatio = false) { - return public_path() . $this->url; + return Images::getThumbnail($this, $width, $height, $keepRatio); } } diff --git a/app/Ownable.php b/app/Ownable.php new file mode 100644 index 000000000..d6505b746 --- /dev/null +++ b/app/Ownable.php @@ -0,0 +1,23 @@ +belongsTo('BookStack\User', 'created_by'); + } + + /** + * Relation for the user that updated this entity. + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function updatedBy() + { + return $this->belongsTo('BookStack\User', 'updated_by'); + } +} \ No newline at end of file diff --git a/app/Page.php b/app/Page.php index feedb1eae..bd5f3bafe 100644 --- a/app/Page.php +++ b/app/Page.php @@ -32,7 +32,6 @@ class Page extends Entity return $this->chapter()->count() > 0; } - public function revisions() { return $this->hasMany('BookStack\PageRevision')->orderBy('created_at', 'desc'); @@ -40,7 +39,6 @@ class Page extends Entity public function getUrl() { - // TODO - Extract this and share with chapters $bookSlug = $this->getAttribute('bookSlug') ? $this->getAttribute('bookSlug') : $this->book->slug; return '/books/' . $bookSlug . '/page/' . $this->slug; } diff --git a/app/Providers/CustomFacadeProvider.php b/app/Providers/CustomFacadeProvider.php index bd4b2b515..1df14a076 100644 --- a/app/Providers/CustomFacadeProvider.php +++ b/app/Providers/CustomFacadeProvider.php @@ -2,6 +2,7 @@ namespace BookStack\Providers; +use BookStack\Services\ImageService; use BookStack\Services\ViewService; use Illuminate\Support\ServiceProvider; use BookStack\Services\ActivityService; @@ -40,5 +41,12 @@ class CustomFacadeProvider extends ServiceProvider $this->app->make('Illuminate\Contracts\Cache\Repository') ); }); + $this->app->bind('images', function() { + return new ImageService( + $this->app->make('Intervention\Image\ImageManager'), + $this->app->make('Illuminate\Contracts\Filesystem\Factory'), + $this->app->make('Illuminate\Contracts\Cache\Repository') + ); + }); } } diff --git a/app/Repos/BookRepo.php b/app/Repos/BookRepo.php index 9be77defe..469fdb31d 100644 --- a/app/Repos/BookRepo.php +++ b/app/Repos/BookRepo.php @@ -77,6 +77,17 @@ class BookRepo return Views::getUserRecentlyViewed($count, $page, $this->book); } + /** + * Gets the most viewed books. + * @param int $count + * @param int $page + * @return mixed + */ + public function getPopular($count = 10, $page = 0) + { + return Views::getPopular($count, $page, $this->book); + } + /** * Get a book by slug * @param $slug diff --git a/app/Repos/ImageRepo.php b/app/Repos/ImageRepo.php new file mode 100644 index 000000000..d41909ac5 --- /dev/null +++ b/app/Repos/ImageRepo.php @@ -0,0 +1,137 @@ +image = $image; + $this->imageService = $imageService; + } + + + /** + * Get an image with the given id. + * @param $id + * @return mixed + */ + public function getById($id) + { + return $this->image->findOrFail($id); + } + + + /** + * Gets a load images paginated, filtered by image type. + * @param string $type + * @param int $page + * @param int $pageSize + * @param bool|int $userFilter + * @return array + */ + public function getPaginatedByType($type, $page = 0, $pageSize = 24, $userFilter = false) + { + $images = $this->image->where('type', '=', strtolower($type)); + + if ($userFilter !== false) { + $images = $images->where('created_by', '=', $userFilter); + } + + $images = $images->orderBy('created_at', 'desc')->skip($pageSize * $page)->take($pageSize + 1)->get(); + $hasMore = count($images) > $pageSize; + + $returnImages = $images->take(24); + $returnImages->each(function ($image) { + $this->loadThumbs($image); + }); + + return [ + 'images' => $returnImages, + 'hasMore' => $hasMore + ]; + } + + /** + * Save a new image into storage and return the new image. + * @param UploadedFile $uploadFile + * @param string $type + * @return Image + */ + public function saveNew(UploadedFile $uploadFile, $type) + { + $image = $this->imageService->saveNewFromUpload($uploadFile, $type); + $this->loadThumbs($image); + return $image; + } + + /** + * Update the details of an image via an array of properties. + * @param Image $image + * @param array $updateDetails + * @return Image + */ + public function updateImageDetails(Image $image, $updateDetails) + { + $image->fill($updateDetails); + $image->save(); + $this->loadThumbs($image); + return $image; + } + + + /** + * Destroys an Image object along with its files and thumbnails. + * @param Image $image + * @return bool + */ + public function destroyImage(Image $image) + { + $this->imageService->destroyImage($image); + return true; + } + + + /** + * Load thumbnails onto an image object. + * @param Image $image + */ + private function loadThumbs(Image $image) + { + $image->thumbs = [ + 'gallery' => $this->getThumbnail($image, 150, 150), + 'display' => $this->getThumbnail($image, 840, 0, true) + ]; + } + + /** + * Get the thumbnail for an image. + * If $keepRatio is true only the width will be used. + * Checks the cache then storage to avoid creating / accessing the filesystem on every check. + * + * @param Image $image + * @param int $width + * @param int $height + * @param bool $keepRatio + * @return string + */ + public function getThumbnail(Image $image, $width = 220, $height = 220, $keepRatio = false) + { + return $this->imageService->getThumbnail($image, $width, $height, $keepRatio); + } + + +} \ No newline at end of file diff --git a/app/Repos/PageRepo.php b/app/Repos/PageRepo.php index a52cecad3..f7b48efdc 100644 --- a/app/Repos/PageRepo.php +++ b/app/Repos/PageRepo.php @@ -269,7 +269,7 @@ class PageRepo * @param Page $page * @return $this */ - private function saveRevision(Page $page) + public function saveRevision(Page $page) { $revision = $this->pageRevision->fill($page->toArray()); $revision->page_id = $page->id; diff --git a/app/Repos/UserRepo.php b/app/Repos/UserRepo.php index 5122aac77..fecd7c88b 100644 --- a/app/Repos/UserRepo.php +++ b/app/Repos/UserRepo.php @@ -46,16 +46,21 @@ class UserRepo public function registerNew(array $data) { $user = $this->create($data); - $roleId = \Setting::get('registration-role'); - - if ($roleId === false) { - $roleId = $this->role->getDefault()->id; - } - - $user->attachRoleId($roleId); + $this->attachDefaultRole($user); return $user; } + /** + * Give a user the default role. Used when creating a new user. + * @param $user + */ + public function attachDefaultRole($user) + { + $roleId = \Setting::get('registration-role'); + if ($roleId === false) $roleId = $this->role->getDefault()->id; + $user->attachRoleId($roleId); + } + /** * Checks if the give user is the only admin. * @param User $user @@ -88,4 +93,14 @@ class UserRepo 'password' => bcrypt($data['password']) ]); } + + /** + * Remove the given user from storage, Delete all related content. + * @param User $user + */ + public function destroy(User $user) + { + $user->socialAccounts()->delete(); + $user->delete(); + } } \ No newline at end of file diff --git a/app/Services/Facades/Images.php b/app/Services/Facades/Images.php new file mode 100644 index 000000000..219f069a0 --- /dev/null +++ b/app/Services/Facades/Images.php @@ -0,0 +1,14 @@ +imageTool = $imageTool; + $this->fileSystem = $fileSystem; + $this->cache = $cache; + } + + /** + * Saves a new image from an upload. + * @param UploadedFile $uploadedFile + * @param string $type + * @return mixed + */ + public function saveNewFromUpload(UploadedFile $uploadedFile, $type) + { + $imageName = $uploadedFile->getClientOriginalName(); + $imageData = file_get_contents($uploadedFile->getRealPath()); + return $this->saveNew($imageName, $imageData, $type); + } + + + /** + * Gets an image from url and saves it to the database. + * @param $url + * @param string $type + * @param bool|string $imageName + * @return mixed + * @throws \Exception + */ + private function saveNewFromUrl($url, $type, $imageName = false) + { + $imageName = $imageName ? $imageName : basename($url); + $imageData = file_get_contents($url); + if($imageData === false) throw new \Exception('Cannot get image from ' . $url); + return $this->saveNew($imageName, $imageData, $type); + } + + /** + * Saves a new image + * @param string $imageName + * @param string $imageData + * @param string $type + * @return Image + */ + private function saveNew($imageName, $imageData, $type) + { + $storage = $this->getStorage(); + $secureUploads = Setting::get('app-secure-images'); + $imageName = str_replace(' ', '-', $imageName); + + if ($secureUploads) $imageName = str_random(16) . '-' . $imageName; + + $imagePath = '/uploads/images/' . $type . '/' . Date('Y-m-M') . '/'; + while ($storage->exists($imagePath . $imageName)) { + $imageName = str_random(3) . $imageName; + } + $fullPath = $imagePath . $imageName; + + $storage->put($fullPath, $imageData); + + $userId = auth()->user()->id; + $image = Image::forceCreate([ + 'name' => $imageName, + 'path' => $fullPath, + 'url' => $this->getPublicUrl($fullPath), + 'type' => $type, + 'created_by' => $userId, + 'updated_by' => $userId + ]); + + return $image; + } + + /** + * Get the thumbnail for an image. + * If $keepRatio is true only the width will be used. + * Checks the cache then storage to avoid creating / accessing the filesystem on every check. + * + * @param Image $image + * @param int $width + * @param int $height + * @param bool $keepRatio + * @return string + */ + 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); + + if ($this->cache->has('images-' . $image->id . '-' . $thumbFilePath) && $this->cache->get('images-' . $thumbFilePath)) { + return $this->getPublicUrl($thumbFilePath); + } + + $storage = $this->getStorage(); + + if ($storage->exists($thumbFilePath)) { + return $this->getPublicUrl($thumbFilePath); + } + + // Otherwise create the thumbnail + $thumb = $this->imageTool->make($storage->get($image->path)); + if ($keepRatio) { + $thumb->resize($width, null, function ($constraint) { + $constraint->aspectRatio(); + $constraint->upsize(); + }); + } else { + $thumb->fit($width, $height); + } + + $thumbData = (string)$thumb->encode(); + $storage->put($thumbFilePath, $thumbData); + $this->cache->put('images-' . $image->id . '-' . $thumbFilePath, $thumbFilePath, 60 * 72); + + return $this->getPublicUrl($thumbFilePath); + } + + /** + * Destroys an Image object along with its files and thumbnails. + * @param Image $image + * @return bool + */ + public function destroyImage(Image $image) + { + $storage = $this->getStorage(); + + $imageFolder = dirname($image->path); + $imageFileName = basename($image->path); + $allImages = collect($storage->allFiles($imageFolder)); + + $imagesToDelete = $allImages->filter(function ($imagePath) use ($imageFileName) { + $expectedIndex = strlen($imagePath) - strlen($imageFileName); + return strpos($imagePath, $imageFileName) === $expectedIndex; + }); + + $storage->delete($imagesToDelete->all()); + + // Cleanup of empty folders + foreach ($storage->directories($imageFolder) as $directory) { + if ($this->isFolderEmpty($directory)) $storage->deleteDirectory($directory); + } + if ($this->isFolderEmpty($imageFolder)) $storage->deleteDirectory($imageFolder); + + $image->delete(); + return true; + } + + /** + * Save a gravatar image and set a the profile image for a user. + * @param User $user + * @param int $size + * @return mixed + */ + public function saveUserGravatar(User $user, $size = 500) + { + $emailHash = md5(strtolower(trim($user->email))); + $url = 'http://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; + $image->save(); + 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 = env('STORAGE_TYPE'); + $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 + * @return string + */ + private function getPublicUrl($filePath) + { + if ($this->storageUrl === null) { + $storageUrl = env('STORAGE_URL'); + + // Get the standard public s3 url if s3 is set as storage type + if ($storageUrl == false && env('STORAGE_TYPE') === 's3') { + $storageDetails = config('filesystems.disks.s3'); + $storageUrl = 'https://s3-' . $storageDetails['region'] . '.amazonaws.com/' . $storageDetails['bucket']; + } + + $this->storageUrl = $storageUrl; + } + + return ($this->storageUrl == false ? '' : rtrim($this->storageUrl, '/')) . $filePath; + } + + +} \ No newline at end of file diff --git a/app/Services/ViewService.php b/app/Services/ViewService.php index 475500927..5b800d939 100644 --- a/app/Services/ViewService.php +++ b/app/Services/ViewService.php @@ -44,6 +44,29 @@ class ViewService return 1; } + + /** + * Get the entities with the most views. + * @param int $count + * @param int $page + * @param bool|false $filterModel + */ + public function getPopular($count = 10, $page = 0, $filterModel = false) + { + $skipCount = $count * $page; + $query = $this->view->select('id', 'viewable_id', 'viewable_type', \DB::raw('SUM(views) as view_count')) + ->groupBy('viewable_id', 'viewable_type') + ->orderBy('view_count', 'desc'); + + if($filterModel) $query->where('viewable_type', '=', get_class($filterModel)); + + $views = $query->with('viewable')->skip($skipCount)->take($count)->get(); + $viewedEntities = $views->map(function ($item) { + return $item->viewable()->getResults(); + }); + return $viewedEntities; + } + /** * Get all recently viewed entities for the current user. * @param int $count diff --git a/app/User.php b/app/User.php index 570789f37..1be98c3c4 100644 --- a/app/User.php +++ b/app/User.php @@ -24,7 +24,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon * * @var array */ - protected $fillable = ['name', 'email', 'password']; + protected $fillable = ['name', 'email', 'password', 'image_id']; /** * The attributes excluded from the model's JSON form. @@ -145,8 +145,17 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon */ public function getAvatar($size = 50) { - $emailHash = md5(strtolower(trim($this->email))); - return '//www.gravatar.com/avatar/' . $emailHash . '?s=' . $size . '&d=identicon'; + if ($this->image_id === 0 || $this->image_id === '0' || $this->image_id === null) return '/user_avatar.png'; + return $this->avatar->getThumb($size, $size, false); + } + + /** + * Get the avatar for the user. + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function avatar() + { + return $this->belongsTo('BookStack\Image', 'image_id'); } /** diff --git a/app/helpers.php b/app/helpers.php new file mode 100644 index 000000000..f25a8f765 --- /dev/null +++ b/app/helpers.php @@ -0,0 +1,30 @@ +=5.5.9", "laravel/framework": "5.1.*", "intervention/image": "^2.3", - "laravel/socialite": "^2.0" + "laravel/socialite": "^2.0", + "barryvdh/laravel-ide-helper": "^2.1", + "barryvdh/laravel-debugbar": "^2.0", + "league/flysystem-aws-s3-v3": "^1.0" }, "require-dev": { "fzaninotto/faker": "~1.4", "mockery/mockery": "0.9.*", "phpunit/phpunit": "~4.0", - "phpspec/phpspec": "~2.1", - "barryvdh/laravel-ide-helper": "^2.1", - "barryvdh/laravel-debugbar": "^2.0" + "phpspec/phpspec": "~2.1" }, "autoload": { "classmap": [ @@ -24,7 +25,10 @@ ], "psr-4": { "BookStack\\": "app/" - } + }, + "files": [ + "app/helpers.php" + ] }, "autoload-dev": { "classmap": [ diff --git a/composer.lock b/composer.lock index 485ee353c..23d2d50b6 100644 --- a/composer.lock +++ b/composer.lock @@ -4,9 +4,87 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "c216d0bcb72b4f2008d7fa8067da2f77", - "content-hash": "3c421ae5b8e5c11792249142cb96ff25", + "hash": "19725116631f01881caafa33052eecb9", + "content-hash": "f1dbd776f0ae13ec99e4e6d99510cd8e", "packages": [ + { + "name": "aws/aws-sdk-php", + "version": "3.11.4", + "source": { + "type": "git", + "url": "https://github.com/aws/aws-sdk-php.git", + "reference": "2524c78e0fa1ed049719b8b6b0696f0b6dfb1ca2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/2524c78e0fa1ed049719b8b6b0696f0b6dfb1ca2", + "reference": "2524c78e0fa1ed049719b8b6b0696f0b6dfb1ca2", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "~5.3|~6.0.1|~6.1", + "guzzlehttp/promises": "~1.0", + "guzzlehttp/psr7": "~1.0", + "mtdowling/jmespath.php": "~2.2", + "php": ">=5.5" + }, + "require-dev": { + "andrewsville/php-token-reflection": "^1.4", + "aws/aws-php-sns-message-validator": "~1.0", + "behat/behat": "~3.0", + "doctrine/cache": "~1.4", + "ext-dom": "*", + "ext-json": "*", + "ext-openssl": "*", + "ext-pcre": "*", + "ext-simplexml": "*", + "ext-spl": "*", + "nette/neon": "^2.3", + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "doctrine/cache": "To use the DoctrineCacheAdapter", + "ext-curl": "To send requests using cURL", + "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Aws\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Amazon Web Services", + "homepage": "http://aws.amazon.com" + } + ], + "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", + "homepage": "http://aws.amazon.com/sdkforphp", + "keywords": [ + "amazon", + "aws", + "cloud", + "dynamodb", + "ec2", + "glacier", + "s3", + "sdk" + ], + "time": "2015-12-04 01:19:53" + }, { "name": "barryvdh/laravel-debugbar", "version": "v2.0.6", @@ -126,29 +204,29 @@ }, { "name": "classpreloader/classpreloader", - "version": "2.0.0", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/ClassPreloader/ClassPreloader.git", - "reference": "8c3c14b10309e3b40bce833913a6c0c0b8c8f962" + "reference": "9b10b913c2bdf90c3d2e0d726b454fb7f77c552a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ClassPreloader/ClassPreloader/zipball/8c3c14b10309e3b40bce833913a6c0c0b8c8f962", - "reference": "8c3c14b10309e3b40bce833913a6c0c0b8c8f962", + "url": "https://api.github.com/repos/ClassPreloader/ClassPreloader/zipball/9b10b913c2bdf90c3d2e0d726b454fb7f77c552a", + "reference": "9b10b913c2bdf90c3d2e0d726b454fb7f77c552a", "shasum": "" }, "require": { - "nikic/php-parser": "~1.3", + "nikic/php-parser": "^1.0|^2.0", "php": ">=5.5.9" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "phpunit/phpunit": "^4.8|^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -176,7 +254,7 @@ "class", "preload" ], - "time": "2015-06-28 21:39:13" + "time": "2015-11-09 22:51:51" }, { "name": "danielstjules/stringy", @@ -269,16 +347,16 @@ }, { "name": "doctrine/inflector", - "version": "v1.0.1", + "version": "v1.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "0bcb2e79d8571787f18b7eb036ed3d004908e604" + "reference": "90b2128806bfde671b6952ab8bea493942c1fdae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/0bcb2e79d8571787f18b7eb036ed3d004908e604", - "reference": "0bcb2e79d8571787f18b7eb036ed3d004908e604", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/90b2128806bfde671b6952ab8bea493942c1fdae", + "reference": "90b2128806bfde671b6952ab8bea493942c1fdae", "shasum": "" }, "require": { @@ -290,7 +368,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.1.x-dev" } }, "autoload": { @@ -332,7 +410,7 @@ "singularize", "string" ], - "time": "2014-12-20 21:24:13" + "time": "2015-11-06 14:35:42" }, { "name": "guzzle/guzzle", @@ -431,16 +509,16 @@ }, { "name": "guzzlehttp/guzzle", - "version": "6.0.2", + "version": "6.1.1", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "a8dfeff00eb84616a17fea7a4d72af35e750410f" + "reference": "c6851d6e48f63b69357cbfa55bca116448140e0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/a8dfeff00eb84616a17fea7a4d72af35e750410f", - "reference": "a8dfeff00eb84616a17fea7a4d72af35e750410f", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/c6851d6e48f63b69357cbfa55bca116448140e0c", + "reference": "c6851d6e48f63b69357cbfa55bca116448140e0c", "shasum": "" }, "require": { @@ -456,7 +534,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "6.0-dev" + "dev-master": "6.1-dev" } }, "autoload": { @@ -489,20 +567,20 @@ "rest", "web service" ], - "time": "2015-07-04 20:09:24" + "time": "2015-11-23 00:47:50" }, { "name": "guzzlehttp/promises", - "version": "1.0.2", + "version": "1.0.3", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "97fe7210def29451ec74923b27e552238defd75a" + "reference": "b1e1c0d55f8083c71eda2c28c12a228d708294ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/97fe7210def29451ec74923b27e552238defd75a", - "reference": "97fe7210def29451ec74923b27e552238defd75a", + "url": "https://api.github.com/repos/guzzle/promises/zipball/b1e1c0d55f8083c71eda2c28c12a228d708294ea", + "reference": "b1e1c0d55f8083c71eda2c28c12a228d708294ea", "shasum": "" }, "require": { @@ -540,20 +618,20 @@ "keywords": [ "promise" ], - "time": "2015-08-15 19:37:21" + "time": "2015-10-15 22:28:00" }, { "name": "guzzlehttp/psr7", - "version": "1.2.0", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "4ef919b0cf3b1989523138b60163bbcb7ba1ff7e" + "reference": "4d0bdbe1206df7440219ce14c972aa57cc5e4982" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/4ef919b0cf3b1989523138b60163bbcb7ba1ff7e", - "reference": "4ef919b0cf3b1989523138b60163bbcb7ba1ff7e", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/4d0bdbe1206df7440219ce14c972aa57cc5e4982", + "reference": "4d0bdbe1206df7440219ce14c972aa57cc5e4982", "shasum": "" }, "require": { @@ -598,20 +676,20 @@ "stream", "uri" ], - "time": "2015-08-15 19:32:36" + "time": "2015-11-03 01:34:55" }, { "name": "intervention/image", - "version": "2.3.1", + "version": "2.3.4", "source": { "type": "git", "url": "https://github.com/Intervention/image.git", - "reference": "156f9d6f8a186c68b92f0c50084718f02dae1b5f" + "reference": "a67ee32df0c6820cc6e861ad4144ee0ef9c74aa3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Intervention/image/zipball/156f9d6f8a186c68b92f0c50084718f02dae1b5f", - "reference": "156f9d6f8a186c68b92f0c50084718f02dae1b5f", + "url": "https://api.github.com/repos/Intervention/image/zipball/a67ee32df0c6820cc6e861ad4144ee0ef9c74aa3", + "reference": "a67ee32df0c6820cc6e861ad4144ee0ef9c74aa3", "shasum": "" }, "require": { @@ -660,7 +738,7 @@ "thumbnail", "watermark" ], - "time": "2015-07-10 15:03:58" + "time": "2015-11-30 17:03:21" }, { "name": "jakub-onderka/php-console-color", @@ -809,20 +887,20 @@ }, { "name": "laravel/framework", - "version": "v5.1.10", + "version": "v5.1.25", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "d47ccc8de10ccb6f328cc90f901ca5e47e077c93" + "reference": "53979acc664debc401bfcb61086c4fc4f196b22d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/d47ccc8de10ccb6f328cc90f901ca5e47e077c93", - "reference": "d47ccc8de10ccb6f328cc90f901ca5e47e077c93", + "url": "https://api.github.com/repos/laravel/framework/zipball/53979acc664debc401bfcb61086c4fc4f196b22d", + "reference": "53979acc664debc401bfcb61086c4fc4f196b22d", "shasum": "" }, "require": { - "classpreloader/classpreloader": "~2.0", + "classpreloader/classpreloader": "~2.0|~3.0", "danielstjules/stringy": "~1.8", "doctrine/inflector": "~1.0", "ext-mbstring": "*", @@ -832,8 +910,9 @@ "monolog/monolog": "~1.11", "mtdowling/cron-expression": "~1.0", "nesbot/carbon": "~1.19", + "paragonie/random_compat": "~1.1", "php": ">=5.5.9", - "psy/psysh": "~0.5.1", + "psy/psysh": "0.6.*", "swiftmailer/swiftmailer": "~5.1", "symfony/console": "2.7.*", "symfony/css-selector": "2.7.*", @@ -882,7 +961,7 @@ "require-dev": { "aws/aws-sdk-php": "~3.0", "iron-io/iron_mq": "~2.0", - "mockery/mockery": "~0.9.1", + "mockery/mockery": "~0.9.2", "pda/pheanstalk": "~3.0", "phpunit/phpunit": "~4.0", "predis/predis": "~1.0" @@ -933,20 +1012,20 @@ "framework", "laravel" ], - "time": "2015-08-12 18:16:08" + "time": "2015-11-30 19:24:36" }, { "name": "laravel/socialite", - "version": "v2.0.12", + "version": "v2.0.14", "source": { "type": "git", "url": "https://github.com/laravel/socialite.git", - "reference": "0bb08c8666f4c01e55e3b3b0e42f2b5075be6a6e" + "reference": "b15f4be0ac739405120d74b837af423aa71502d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/0bb08c8666f4c01e55e3b3b0e42f2b5075be6a6e", - "reference": "0bb08c8666f4c01e55e3b3b0e42f2b5075be6a6e", + "url": "https://api.github.com/repos/laravel/socialite/zipball/b15f4be0ac739405120d74b837af423aa71502d9", + "reference": "b15f4be0ac739405120d74b837af423aa71502d9", "shasum": "" }, "require": { @@ -987,25 +1066,28 @@ "laravel", "oauth" ], - "time": "2015-08-30 01:12:56" + "time": "2015-10-16 15:39:46" }, { "name": "league/flysystem", - "version": "1.0.11", + "version": "1.0.15", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "c16222fdc02467eaa12cb6d6d0e65527741f6040" + "reference": "31525caf9e8772683672fefd8a1ca0c0736020f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/c16222fdc02467eaa12cb6d6d0e65527741f6040", - "reference": "c16222fdc02467eaa12cb6d6d0e65527741f6040", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/31525caf9e8772683672fefd8a1ca0c0736020f4", + "reference": "31525caf9e8772683672fefd8a1ca0c0736020f4", "shasum": "" }, "require": { "php": ">=5.4.0" }, + "conflict": { + "league/flysystem-sftp": "<1.0.6" + }, "require-dev": { "ext-fileinfo": "*", "mockery/mockery": "~0.9", @@ -1068,20 +1150,67 @@ "sftp", "storage" ], - "time": "2015-07-28 20:41:58" + "time": "2015-09-30 22:26:59" }, { - "name": "league/oauth1-client", - "version": "1.6.0", + "name": "league/flysystem-aws-s3-v3", + "version": "1.0.9", "source": { "type": "git", - "url": "https://github.com/thephpleague/oauth1-client.git", - "reference": "4d4edd9b6014f882e319231a9b3351e3a1dfdc81" + "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", + "reference": "595e24678bf78f8107ebc9355d8376ae0eb712c6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/4d4edd9b6014f882e319231a9b3351e3a1dfdc81", - "reference": "4d4edd9b6014f882e319231a9b3351e3a1dfdc81", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/595e24678bf78f8107ebc9355d8376ae0eb712c6", + "reference": "595e24678bf78f8107ebc9355d8376ae0eb712c6", + "shasum": "" + }, + "require": { + "aws/aws-sdk-php": "^3.0.0", + "league/flysystem": "~1.0", + "php": ">=5.5.0" + }, + "require-dev": { + "henrikbjorn/phpspec-code-coverage": "~1.0.1", + "phpspec/phpspec": "^2.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\AwsS3v3\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Flysystem adapter for the AWS S3 SDK v3.x", + "time": "2015-11-19 08:44:16" + }, + { + "name": "league/oauth1-client", + "version": "1.6.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth1-client.git", + "reference": "cef3ceda13c78f89c323e4d5e6301c0eb7cea422" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/cef3ceda13c78f89c323e4d5e6301c0eb7cea422", + "reference": "cef3ceda13c78f89c323e4d5e6301c0eb7cea422", "shasum": "" }, "require": { @@ -1131,7 +1260,7 @@ "tumblr", "twitter" ], - "time": "2015-08-22 09:49:14" + "time": "2015-10-23 04:02:07" }, { "name": "maximebf/debugbar", @@ -1191,16 +1320,16 @@ }, { "name": "monolog/monolog", - "version": "1.16.0", + "version": "1.17.2", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "c0c0b4bee3aabce7182876b0d912ef2595563db7" + "reference": "bee7f0dc9c3e0b69a6039697533dca1e845c8c24" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/c0c0b4bee3aabce7182876b0d912ef2595563db7", - "reference": "c0c0b4bee3aabce7182876b0d912ef2595563db7", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/bee7f0dc9c3e0b69a6039697533dca1e845c8c24", + "reference": "bee7f0dc9c3e0b69a6039697533dca1e845c8c24", "shasum": "" }, "require": { @@ -1214,10 +1343,11 @@ "aws/aws-sdk-php": "^2.4.9", "doctrine/couchdb": "~1.0@dev", "graylog2/gelf-php": "~1.0", + "jakub-onderka/php-parallel-lint": "0.9", "php-console/php-console": "^3.1.3", "phpunit/phpunit": "~4.5", "phpunit/phpunit-mock-objects": "2.3.0", - "raven/raven": "~0.8", + "raven/raven": "^0.13", "ruflin/elastica": ">=0.90 <3.0", "swiftmailer/swiftmailer": "~5.3", "videlalvaro/php-amqplib": "~2.4" @@ -1263,7 +1393,7 @@ "logging", "psr-3" ], - "time": "2015-08-09 17:44:44" + "time": "2015-10-14 12:51:02" }, { "name": "mtdowling/cron-expression", @@ -1310,17 +1440,72 @@ "time": "2015-01-11 23:07:46" }, { - "name": "nesbot/carbon", - "version": "1.20.0", + "name": "mtdowling/jmespath.php", + "version": "2.2.0", "source": { "type": "git", - "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "bfd3eaba109c9a2405c92174c8e17f20c2b9caf3" + "url": "https://github.com/jmespath/jmespath.php.git", + "reference": "a7d99d0c836e69d27b7bfca1d33ca2759fba3289" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/bfd3eaba109c9a2405c92174c8e17f20c2b9caf3", - "reference": "bfd3eaba109c9a2405c92174c8e17f20c2b9caf3", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/a7d99d0c836e69d27b7bfca1d33ca2759fba3289", + "reference": "a7d99d0c836e69d27b7bfca1d33ca2759fba3289", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "bin": [ + "bin/jp.php" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "JmesPath\\": "src/" + }, + "files": [ + "src/JmesPath.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Declaratively specify how to extract elements from a JSON document", + "keywords": [ + "json", + "jsonpath" + ], + "time": "2015-05-27 17:21:31" + }, + { + "name": "nesbot/carbon", + "version": "1.21.0", + "source": { + "type": "git", + "url": "https://github.com/briannesbitt/Carbon.git", + "reference": "7b08ec6f75791e130012f206e3f7b0e76e18e3d7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/7b08ec6f75791e130012f206e3f7b0e76e18e3d7", + "reference": "7b08ec6f75791e130012f206e3f7b0e76e18e3d7", "shasum": "" }, "require": { @@ -1328,12 +1513,12 @@ "symfony/translation": "~2.6|~3.0" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "phpunit/phpunit": "~4.0|~5.0" }, "type": "library", "autoload": { - "psr-0": { - "Carbon": "src" + "psr-4": { + "Carbon\\": "src/Carbon/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1354,20 +1539,20 @@ "datetime", "time" ], - "time": "2015-06-25 04:19:39" + "time": "2015-11-04 20:07:17" }, { "name": "nikic/php-parser", - "version": "v1.4.0", + "version": "v1.4.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "196f177cfefa0f1f7166c0a05d8255889be12418" + "reference": "f78af2c9c86107aa1a34cd1dbb5bbe9eeb0d9f51" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/196f177cfefa0f1f7166c0a05d8255889be12418", - "reference": "196f177cfefa0f1f7166c0a05d8255889be12418", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f78af2c9c86107aa1a34cd1dbb5bbe9eeb0d9f51", + "reference": "f78af2c9c86107aa1a34cd1dbb5bbe9eeb0d9f51", "shasum": "" }, "require": { @@ -1399,7 +1584,55 @@ "parser", "php" ], - "time": "2015-07-14 17:31:05" + "time": "2015-09-19 14:15:08" + }, + { + "name": "paragonie/random_compat", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "a208865a5aeffc2dbbef2a5b3409887272d93f32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/a208865a5aeffc2dbbef2a5b3409887272d93f32", + "reference": "a208865a5aeffc2dbbef2a5b3409887272d93f32", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "autoload": { + "files": [ + "lib/random.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "pseudorandom", + "random" + ], + "time": "2015-12-01 02:52:15" }, { "name": "phpdocumentor/reflection-docblock", @@ -1539,29 +1772,29 @@ }, { "name": "psy/psysh", - "version": "v0.5.2", + "version": "v0.6.1", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "aaf8772ade08b5f0f6830774a5d5c2f800415975" + "reference": "0f04df0b23663799a8941fae13cd8e6299bde3ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/aaf8772ade08b5f0f6830774a5d5c2f800415975", - "reference": "aaf8772ade08b5f0f6830774a5d5c2f800415975", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/0f04df0b23663799a8941fae13cd8e6299bde3ed", + "reference": "0f04df0b23663799a8941fae13cd8e6299bde3ed", "shasum": "" }, "require": { "dnoegel/php-xdg-base-dir": "0.1", "jakub-onderka/php-console-highlighter": "0.3.*", - "nikic/php-parser": "^1.2.1", + "nikic/php-parser": "^1.2.1|~2.0", "php": ">=5.3.9", "symfony/console": "~2.3.10|^2.4.2|~3.0", "symfony/var-dumper": "~2.7|~3.0" }, "require-dev": { "fabpot/php-cs-fixer": "~1.5", - "phpunit/phpunit": "~3.7|~4.0", + "phpunit/phpunit": "~3.7|~4.0|~5.0", "squizlabs/php_codesniffer": "~2.0", "symfony/finder": "~2.1|~3.0" }, @@ -1577,15 +1810,15 @@ "type": "library", "extra": { "branch-alias": { - "dev-develop": "0.6.x-dev" + "dev-develop": "0.7.x-dev" } }, "autoload": { "files": [ "src/Psy/functions.php" ], - "psr-0": { - "Psy\\": "src/" + "psr-4": { + "Psy\\": "src/Psy/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1607,7 +1840,7 @@ "interactive", "shell" ], - "time": "2015-07-16 15:26:57" + "time": "2015-11-12 16:18:56" }, { "name": "swiftmailer/swiftmailer", @@ -1664,35 +1897,37 @@ }, { "name": "symfony/class-loader", - "version": "v2.7.3", + "version": "v2.8.0", "source": { "type": "git", "url": "https://github.com/symfony/class-loader.git", - "reference": "2fccbc544997340808801a7410cdcb96dd12edc4" + "reference": "51f83451bf0ddfc696e47e4642d6cd10fcfce160" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/class-loader/zipball/2fccbc544997340808801a7410cdcb96dd12edc4", - "reference": "2fccbc544997340808801a7410cdcb96dd12edc4", + "url": "https://api.github.com/repos/symfony/class-loader/zipball/51f83451bf0ddfc696e47e4642d6cd10fcfce160", + "reference": "51f83451bf0ddfc696e47e4642d6cd10fcfce160", "shasum": "" }, "require": { "php": ">=5.3.9" }, "require-dev": { - "symfony/finder": "~2.0,>=2.0.5", - "symfony/phpunit-bridge": "~2.7" + "symfony/finder": "~2.0,>=2.0.5|~3.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } }, "autoload": { "psr-4": { "Symfony\\Component\\ClassLoader\\": "" - } + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1710,20 +1945,20 @@ ], "description": "Symfony ClassLoader Component", "homepage": "https://symfony.com", - "time": "2015-06-25 12:52:11" + "time": "2015-11-26 07:00:59" }, { "name": "symfony/console", - "version": "v2.7.3", + "version": "v2.7.7", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "d6cf02fe73634c96677e428f840704bfbcaec29e" + "reference": "16bb1cb86df43c90931df65f529e7ebd79636750" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/d6cf02fe73634c96677e428f840704bfbcaec29e", - "reference": "d6cf02fe73634c96677e428f840704bfbcaec29e", + "url": "https://api.github.com/repos/symfony/console/zipball/16bb1cb86df43c90931df65f529e7ebd79636750", + "reference": "16bb1cb86df43c90931df65f529e7ebd79636750", "shasum": "" }, "require": { @@ -1732,7 +1967,6 @@ "require-dev": { "psr/log": "~1.0", "symfony/event-dispatcher": "~2.1", - "symfony/phpunit-bridge": "~2.7", "symfony/process": "~2.1" }, "suggest": { @@ -1749,7 +1983,10 @@ "autoload": { "psr-4": { "Symfony\\Component\\Console\\": "" - } + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1767,28 +2004,25 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2015-07-28 15:18:12" + "time": "2015-11-18 09:54:26" }, { "name": "symfony/css-selector", - "version": "v2.7.3", + "version": "v2.7.7", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "0b5c07b516226b7dd32afbbc82fe547a469c5092" + "reference": "abb47717fb88aebd9437da2fc8bb01a50a36679f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/0b5c07b516226b7dd32afbbc82fe547a469c5092", - "reference": "0b5c07b516226b7dd32afbbc82fe547a469c5092", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/abb47717fb88aebd9437da2fc8bb01a50a36679f", + "reference": "abb47717fb88aebd9437da2fc8bb01a50a36679f", "shasum": "" }, "require": { "php": ">=5.3.9" }, - "require-dev": { - "symfony/phpunit-bridge": "~2.7" - }, "type": "library", "extra": { "branch-alias": { @@ -1798,7 +2032,10 @@ "autoload": { "psr-4": { "Symfony\\Component\\CssSelector\\": "" - } + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1820,20 +2057,20 @@ ], "description": "Symfony CssSelector Component", "homepage": "https://symfony.com", - "time": "2015-05-15 13:33:16" + "time": "2015-10-30 20:10:21" }, { "name": "symfony/debug", - "version": "v2.7.3", + "version": "v2.7.7", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "9daa1bf9f7e615fa2fba30357e479a90141222e3" + "reference": "0dbc119596f4afc82d9b2eb2a7e6a4af1ee763fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/9daa1bf9f7e615fa2fba30357e479a90141222e3", - "reference": "9daa1bf9f7e615fa2fba30357e479a90141222e3", + "url": "https://api.github.com/repos/symfony/debug/zipball/0dbc119596f4afc82d9b2eb2a7e6a4af1ee763fa", + "reference": "0dbc119596f4afc82d9b2eb2a7e6a4af1ee763fa", "shasum": "" }, "require": { @@ -1845,13 +2082,7 @@ }, "require-dev": { "symfony/class-loader": "~2.2", - "symfony/http-foundation": "~2.1", - "symfony/http-kernel": "~2.3.24|~2.5.9|~2.6,>=2.6.2", - "symfony/phpunit-bridge": "~2.7" - }, - "suggest": { - "symfony/http-foundation": "", - "symfony/http-kernel": "" + "symfony/http-kernel": "~2.3.24|~2.5.9|~2.6,>=2.6.2" }, "type": "library", "extra": { @@ -1862,7 +2093,10 @@ "autoload": { "psr-4": { "Symfony\\Component\\Debug\\": "" - } + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1880,28 +2114,27 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2015-07-09 16:07:40" + "time": "2015-10-30 20:10:21" }, { "name": "symfony/dom-crawler", - "version": "v2.7.3", + "version": "v2.7.7", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "9dabece63182e95c42b06967a0d929a5df78bc35" + "reference": "b33593cbfe1d81b50d48353f338aca76a08658d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/9dabece63182e95c42b06967a0d929a5df78bc35", - "reference": "9dabece63182e95c42b06967a0d929a5df78bc35", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/b33593cbfe1d81b50d48353f338aca76a08658d8", + "reference": "b33593cbfe1d81b50d48353f338aca76a08658d8", "shasum": "" }, "require": { "php": ">=5.3.9" }, "require-dev": { - "symfony/css-selector": "~2.3", - "symfony/phpunit-bridge": "~2.7" + "symfony/css-selector": "~2.3" }, "suggest": { "symfony/css-selector": "" @@ -1915,7 +2148,10 @@ "autoload": { "psr-4": { "Symfony\\Component\\DomCrawler\\": "" - } + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1933,20 +2169,20 @@ ], "description": "Symfony DomCrawler Component", "homepage": "https://symfony.com", - "time": "2015-07-09 16:07:40" + "time": "2015-11-02 20:20:53" }, { "name": "symfony/event-dispatcher", - "version": "v2.7.3", + "version": "v2.8.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "9310b5f9a87ec2ea75d20fec0b0017c77c66dac3" + "reference": "a5eb815363c0388e83247e7e9853e5dbc14999cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9310b5f9a87ec2ea75d20fec0b0017c77c66dac3", - "reference": "9310b5f9a87ec2ea75d20fec0b0017c77c66dac3", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a5eb815363c0388e83247e7e9853e5dbc14999cc", + "reference": "a5eb815363c0388e83247e7e9853e5dbc14999cc", "shasum": "" }, "require": { @@ -1954,11 +2190,10 @@ }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "~2.0,>=2.0.5", - "symfony/dependency-injection": "~2.6", - "symfony/expression-language": "~2.6", - "symfony/phpunit-bridge": "~2.7", - "symfony/stopwatch": "~2.3" + "symfony/config": "~2.0,>=2.0.5|~3.0.0", + "symfony/dependency-injection": "~2.6|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0", + "symfony/stopwatch": "~2.3|~3.0.0" }, "suggest": { "symfony/dependency-injection": "", @@ -1967,13 +2202,16 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } }, "autoload": { "psr-4": { "Symfony\\Component\\EventDispatcher\\": "" - } + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1991,28 +2229,25 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2015-06-18 19:21:56" + "time": "2015-10-30 20:15:42" }, { "name": "symfony/finder", - "version": "v2.7.3", + "version": "v2.7.7", "source": { "type": "git", - "url": "https://github.com/symfony/Finder.git", - "reference": "ae0f363277485094edc04c9f3cbe595b183b78e4" + "url": "https://github.com/symfony/finder.git", + "reference": "a06a0c0ff7db3736a50d530c908cca547bf13da9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Finder/zipball/ae0f363277485094edc04c9f3cbe595b183b78e4", - "reference": "ae0f363277485094edc04c9f3cbe595b183b78e4", + "url": "https://api.github.com/repos/symfony/finder/zipball/a06a0c0ff7db3736a50d530c908cca547bf13da9", + "reference": "a06a0c0ff7db3736a50d530c908cca547bf13da9", "shasum": "" }, "require": { "php": ">=5.3.9" }, - "require-dev": { - "symfony/phpunit-bridge": "~2.7" - }, "type": "library", "extra": { "branch-alias": { @@ -2022,7 +2257,10 @@ "autoload": { "psr-4": { "Symfony\\Component\\Finder\\": "" - } + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2040,28 +2278,27 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2015-07-09 16:07:40" + "time": "2015-10-30 20:10:21" }, { "name": "symfony/http-foundation", - "version": "v2.7.3", + "version": "v2.7.7", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "863af6898081b34c65d42100c370b9f3c51b70ca" + "reference": "e83a3d105ddaf5a113e803c904fdec552d1f1c35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/863af6898081b34c65d42100c370b9f3c51b70ca", - "reference": "863af6898081b34c65d42100c370b9f3c51b70ca", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/e83a3d105ddaf5a113e803c904fdec552d1f1c35", + "reference": "e83a3d105ddaf5a113e803c904fdec552d1f1c35", "shasum": "" }, "require": { "php": ">=5.3.9" }, "require-dev": { - "symfony/expression-language": "~2.4", - "symfony/phpunit-bridge": "~2.7" + "symfony/expression-language": "~2.4" }, "type": "library", "extra": { @@ -2075,6 +2312,9 @@ }, "classmap": [ "Resources/stubs" + ], + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -2093,20 +2333,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2015-07-22 10:11:00" + "time": "2015-11-20 17:41:18" }, { "name": "symfony/http-kernel", - "version": "v2.7.3", + "version": "v2.7.7", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "405d3e7a59ff7a28ec469441326a0ac79065ea98" + "reference": "5570de31e8fbc03777a8c61eb24f9b626e5e5941" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/405d3e7a59ff7a28ec469441326a0ac79065ea98", - "reference": "405d3e7a59ff7a28ec469441326a0ac79065ea98", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/5570de31e8fbc03777a8c61eb24f9b626e5e5941", + "reference": "5570de31e8fbc03777a8c61eb24f9b626e5e5941", "shasum": "" }, "require": { @@ -2129,7 +2369,6 @@ "symfony/dom-crawler": "~2.0,>=2.0.5", "symfony/expression-language": "~2.4", "symfony/finder": "~2.0,>=2.0.5", - "symfony/phpunit-bridge": "~2.7", "symfony/process": "~2.0,>=2.0.5", "symfony/routing": "~2.2", "symfony/stopwatch": "~2.3", @@ -2155,7 +2394,10 @@ "autoload": { "psr-4": { "Symfony\\Component\\HttpKernel\\": "" - } + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2173,28 +2415,25 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2015-07-31 13:24:45" + "time": "2015-11-23 11:57:49" }, { "name": "symfony/process", - "version": "v2.7.3", + "version": "v2.7.7", "source": { "type": "git", - "url": "https://github.com/symfony/Process.git", - "reference": "48aeb0e48600321c272955132d7606ab0a49adb3" + "url": "https://github.com/symfony/process.git", + "reference": "f6290983c8725d0afa29bdc3e5295879de3e58f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Process/zipball/48aeb0e48600321c272955132d7606ab0a49adb3", - "reference": "48aeb0e48600321c272955132d7606ab0a49adb3", + "url": "https://api.github.com/repos/symfony/process/zipball/f6290983c8725d0afa29bdc3e5295879de3e58f5", + "reference": "f6290983c8725d0afa29bdc3e5295879de3e58f5", "shasum": "" }, "require": { "php": ">=5.3.9" }, - "require-dev": { - "symfony/phpunit-bridge": "~2.7" - }, "type": "library", "extra": { "branch-alias": { @@ -2204,7 +2443,10 @@ "autoload": { "psr-4": { "Symfony\\Component\\Process\\": "" - } + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2222,20 +2464,20 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2015-07-01 11:25:50" + "time": "2015-11-19 16:11:24" }, { "name": "symfony/routing", - "version": "v2.7.3", + "version": "v2.7.7", "source": { "type": "git", - "url": "https://github.com/symfony/Routing.git", - "reference": "ea9134f277162b02e5f80ac058b75a77637b0d26" + "url": "https://github.com/symfony/routing.git", + "reference": "7450f6196711b124fb8b04a12286d01a0401ddfe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Routing/zipball/ea9134f277162b02e5f80ac058b75a77637b0d26", - "reference": "ea9134f277162b02e5f80ac058b75a77637b0d26", + "url": "https://api.github.com/repos/symfony/routing/zipball/7450f6196711b124fb8b04a12286d01a0401ddfe", + "reference": "7450f6196711b124fb8b04a12286d01a0401ddfe", "shasum": "" }, "require": { @@ -2251,7 +2493,6 @@ "symfony/config": "~2.7", "symfony/expression-language": "~2.4", "symfony/http-foundation": "~2.3", - "symfony/phpunit-bridge": "~2.7", "symfony/yaml": "~2.0,>=2.0.5" }, "suggest": { @@ -2269,7 +2510,10 @@ "autoload": { "psr-4": { "Symfony\\Component\\Routing\\": "" - } + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2293,20 +2537,20 @@ "uri", "url" ], - "time": "2015-07-09 16:07:40" + "time": "2015-11-18 13:41:01" }, { "name": "symfony/translation", - "version": "v2.7.3", + "version": "v2.7.7", "source": { "type": "git", - "url": "https://github.com/symfony/Translation.git", - "reference": "c8dc34cc936152c609cdd722af317e4239d10dd6" + "url": "https://github.com/symfony/translation.git", + "reference": "e4ecb9c3ba1304eaf24de15c2d7a428101c1982f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Translation/zipball/c8dc34cc936152c609cdd722af317e4239d10dd6", - "reference": "c8dc34cc936152c609cdd722af317e4239d10dd6", + "url": "https://api.github.com/repos/symfony/translation/zipball/e4ecb9c3ba1304eaf24de15c2d7a428101c1982f", + "reference": "e4ecb9c3ba1304eaf24de15c2d7a428101c1982f", "shasum": "" }, "require": { @@ -2318,8 +2562,7 @@ "require-dev": { "psr/log": "~1.0", "symfony/config": "~2.7", - "symfony/intl": "~2.3", - "symfony/phpunit-bridge": "~2.7", + "symfony/intl": "~2.4", "symfony/yaml": "~2.2" }, "suggest": { @@ -2336,7 +2579,10 @@ "autoload": { "psr-4": { "Symfony\\Component\\Translation\\": "" - } + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2354,28 +2600,25 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", - "time": "2015-07-09 16:07:40" + "time": "2015-11-18 13:41:01" }, { "name": "symfony/var-dumper", - "version": "v2.7.3", + "version": "v2.7.7", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "e8903ebba5eb019f5886ffce739ea9e3b7519579" + "reference": "72bcb27411780eaee9469729aace73c0d46fb2b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/e8903ebba5eb019f5886ffce739ea9e3b7519579", - "reference": "e8903ebba5eb019f5886ffce739ea9e3b7519579", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/72bcb27411780eaee9469729aace73c0d46fb2b8", + "reference": "72bcb27411780eaee9469729aace73c0d46fb2b8", "shasum": "" }, "require": { "php": ">=5.3.9" }, - "require-dev": { - "symfony/phpunit-bridge": "~2.7" - }, "suggest": { "ext-symfony_debug": "" }, @@ -2391,7 +2634,10 @@ ], "psr-4": { "Symfony\\Component\\VarDumper\\": "" - } + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2413,7 +2659,7 @@ "debug", "dump" ], - "time": "2015-07-28 15:18:12" + "time": "2015-11-18 13:41:01" }, { "name": "vlucas/phpdotenv", @@ -2715,36 +2961,36 @@ }, { "name": "phpspec/phpspec", - "version": "2.2.1", + "version": "2.4.0", "source": { "type": "git", "url": "https://github.com/phpspec/phpspec.git", - "reference": "e9a40577323e67f1de2e214abf32976a0352d8f8" + "reference": "1d3938e6d9ffb1bd4805ea8ddac62ea48767f358" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/phpspec/zipball/e9a40577323e67f1de2e214abf32976a0352d8f8", - "reference": "e9a40577323e67f1de2e214abf32976a0352d8f8", + "url": "https://api.github.com/repos/phpspec/phpspec/zipball/1d3938e6d9ffb1bd4805ea8ddac62ea48767f358", + "reference": "1d3938e6d9ffb1bd4805ea8ddac62ea48767f358", "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", - "symfony/event-dispatcher": "~2.1", - "symfony/finder": "~2.1", - "symfony/process": "~2.1", - "symfony/yaml": "~2.1" + "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" }, "require-dev": { "behat/behat": "^3.0.11", "bossa/phpspec2-expect": "~1.0", "phpunit/phpunit": "~4.4", - "symfony/filesystem": "~2.1", - "symfony/process": "~2.1" + "symfony/filesystem": "~2.1|~3.0" }, "suggest": { "phpspec/nyan-formatters": "~1.0 – Adds Nyan formatters" @@ -2789,7 +3035,7 @@ "testing", "tests" ], - "time": "2015-05-30 15:21:40" + "time": "2015-11-29 02:03:49" }, { "name": "phpspec/prophecy", @@ -2853,16 +3099,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "2.2.2", + "version": "2.2.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "2d7c03c0e4e080901b8f33b2897b0577be18a13c" + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2d7c03c0e4e080901b8f33b2897b0577be18a13c", - "reference": "2d7c03c0e4e080901b8f33b2897b0577be18a13c", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979", "shasum": "" }, "require": { @@ -2911,7 +3157,7 @@ "testing", "xunit" ], - "time": "2015-08-04 03:42:39" + "time": "2015-10-06 15:47:00" }, { "name": "phpunit/php-file-iterator", @@ -3044,16 +3290,16 @@ }, { "name": "phpunit/php-token-stream", - "version": "1.4.6", + "version": "1.4.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "3ab72c62e550370a6cd5dc873e1a04ab57562f5b" + "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3ab72c62e550370a6cd5dc873e1a04ab57562f5b", - "reference": "3ab72c62e550370a6cd5dc873e1a04ab57562f5b", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", + "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", "shasum": "" }, "require": { @@ -3089,20 +3335,20 @@ "keywords": [ "tokenizer" ], - "time": "2015-08-16 08:51:00" + "time": "2015-09-15 10:49:45" }, { "name": "phpunit/phpunit", - "version": "4.8.4", + "version": "4.8.19", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "55bf1d6092b0e13a1f26bd5eaffeef3d8ad85ea7" + "reference": "b2caaf8947aba5e002d42126723e9d69795f32b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/55bf1d6092b0e13a1f26bd5eaffeef3d8ad85ea7", - "reference": "55bf1d6092b0e13a1f26bd5eaffeef3d8ad85ea7", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b2caaf8947aba5e002d42126723e9d69795f32b4", + "reference": "b2caaf8947aba5e002d42126723e9d69795f32b4", "shasum": "" }, "require": { @@ -3161,24 +3407,24 @@ "testing", "xunit" ], - "time": "2015-08-15 04:21:23" + "time": "2015-11-30 08:18:59" }, { "name": "phpunit/phpunit-mock-objects", - "version": "2.3.6", + "version": "2.3.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "18dfbcb81d05e2296c0bcddd4db96cade75e6f42" + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/18dfbcb81d05e2296c0bcddd4db96cade75e6f42", - "reference": "18dfbcb81d05e2296c0bcddd4db96cade75e6f42", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983", "shasum": "" }, "require": { - "doctrine/instantiator": "~1.0,>=1.0.2", + "doctrine/instantiator": "^1.0.2", "php": ">=5.3.3", "phpunit/php-text-template": "~1.2", "sebastian/exporter": "~1.2" @@ -3217,7 +3463,7 @@ "mock", "xunit" ], - "time": "2015-07-10 06:54:24" + "time": "2015-10-02 06:51:40" }, { "name": "sebastian/comparator", @@ -3337,16 +3583,16 @@ }, { "name": "sebastian/environment", - "version": "1.3.2", + "version": "1.3.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "6324c907ce7a52478eeeaede764f48733ef5ae44" + "reference": "6e7133793a8e5a5714a551a8324337374be209df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6324c907ce7a52478eeeaede764f48733ef5ae44", - "reference": "6324c907ce7a52478eeeaede764f48733ef5ae44", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6e7133793a8e5a5714a551a8324337374be209df", + "reference": "6e7133793a8e5a5714a551a8324337374be209df", "shasum": "" }, "require": { @@ -3383,7 +3629,7 @@ "environment", "hhvm" ], - "time": "2015-08-03 06:14:51" + "time": "2015-12-02 08:37:27" }, { "name": "sebastian/exporter", @@ -3453,16 +3699,16 @@ }, { "name": "sebastian/global-state", - "version": "1.0.0", + "version": "1.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "c7428acdb62ece0a45e6306f1ae85e1c05b09c01" + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/c7428acdb62ece0a45e6306f1ae85e1c05b09c01", - "reference": "c7428acdb62ece0a45e6306f1ae85e1c05b09c01", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", "shasum": "" }, "require": { @@ -3500,7 +3746,7 @@ "keywords": [ "global state" ], - "time": "2014-10-06 09:23:50" + "time": "2015-10-12 03:26:01" }, { "name": "sebastian/recursion-context", @@ -3592,34 +3838,34 @@ }, { "name": "symfony/yaml", - "version": "v2.7.3", + "version": "v3.0.0", "source": { "type": "git", - "url": "https://github.com/symfony/Yaml.git", - "reference": "71340e996171474a53f3d29111d046be4ad8a0ff" + "url": "https://github.com/symfony/yaml.git", + "reference": "177a015cb0e19ff4a49e0e2e2c5fc1c1bee07002" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Yaml/zipball/71340e996171474a53f3d29111d046be4ad8a0ff", - "reference": "71340e996171474a53f3d29111d046be4ad8a0ff", + "url": "https://api.github.com/repos/symfony/yaml/zipball/177a015cb0e19ff4a49e0e2e2c5fc1c1bee07002", + "reference": "177a015cb0e19ff4a49e0e2e2c5fc1c1bee07002", "shasum": "" }, "require": { - "php": ">=5.3.9" - }, - "require-dev": { - "symfony/phpunit-bridge": "~2.7" + "php": ">=5.5.9" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "3.0-dev" } }, "autoload": { "psr-4": { "Symfony\\Component\\Yaml\\": "" - } + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3637,7 +3883,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2015-07-28 14:07:07" + "time": "2015-11-30 12:36:17" } ], "aliases": [], diff --git a/config/app.php b/config/app.php index aa7c0b561..b9c207632 100644 --- a/config/app.php +++ b/config/app.php @@ -13,7 +13,7 @@ return [ | */ - 'debug' => env('APP_DEBUG', false), + 'debug' => env('APP_DEBUG', false), /* |-------------------------------------------------------------------------- @@ -26,7 +26,7 @@ return [ | */ - 'url' => env('APP_URL', 'http://localhost'), + 'url' => env('APP_URL', 'http://localhost'), /* |-------------------------------------------------------------------------- @@ -39,7 +39,7 @@ return [ | */ - 'timezone' => 'UTC', + 'timezone' => 'UTC', /* |-------------------------------------------------------------------------- @@ -52,7 +52,7 @@ return [ | */ - 'locale' => 'en', + 'locale' => 'en', /* |-------------------------------------------------------------------------- @@ -78,9 +78,9 @@ return [ | */ - 'key' => env('APP_KEY', 'AbAZchsay4uBTU33RubBzLKw203yqSqr'), + 'key' => env('APP_KEY', 'AbAZchsay4uBTU33RubBzLKw203yqSqr'), - 'cipher' => 'AES-256-CBC', + 'cipher' => 'AES-256-CBC', /* |-------------------------------------------------------------------------- @@ -95,7 +95,7 @@ return [ | */ - 'log' => 'single', + 'log' => 'single', /* |-------------------------------------------------------------------------- @@ -108,7 +108,7 @@ return [ | */ - 'providers' => [ + 'providers' => [ /* * Laravel Framework Service Providers... @@ -167,7 +167,7 @@ return [ | */ - 'aliases' => [ + 'aliases' => [ 'App' => Illuminate\Support\Facades\App::class, 'Artisan' => Illuminate\Support\Facades\Artisan::class, @@ -208,15 +208,16 @@ return [ */ 'ImageTool' => Intervention\Image\Facades\Image::class, - 'Debugbar' => Barryvdh\Debugbar\Facade::class, + 'Debugbar' => Barryvdh\Debugbar\Facade::class, /** * Custom */ - 'Activity' => BookStack\Services\Facades\Activity::class, - 'Setting' => BookStack\Services\Facades\Setting::class, - 'Views' => BookStack\Services\Facades\Views::class, + 'Activity' => BookStack\Services\Facades\Activity::class, + 'Setting' => BookStack\Services\Facades\Setting::class, + 'Views' => BookStack\Services\Facades\Views::class, + 'Images' => \BookStack\Services\Facades\Images::class, ], diff --git a/config/filesystems.php b/config/filesystems.php index 3fffcf0a2..5fd3df6df 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -45,7 +45,7 @@ return [ 'local' => [ 'driver' => 'local', - 'root' => storage_path('app'), + 'root' => public_path(), ], 'ftp' => [ @@ -64,10 +64,10 @@ return [ 's3' => [ 'driver' => 's3', - 'key' => 'your-key', - 'secret' => 'your-secret', - 'region' => 'your-region', - 'bucket' => 'your-bucket', + 'key' => env('STORAGE_S3_KEY', 'your-key'), + 'secret' => env('STORAGE_S3_SECRET', 'your-secret'), + 'region' => env('STORAGE_S3_REGION', 'your-region'), + 'bucket' => env('STORAGE_S3_BUCKET', 'your-bucket'), ], 'rackspace' => [ diff --git a/database/migrations/2015_12_05_145049_fulltext_weighting.php b/database/migrations/2015_12_05_145049_fulltext_weighting.php new file mode 100644 index 000000000..cef43f604 --- /dev/null +++ b/database/migrations/2015_12_05_145049_fulltext_weighting.php @@ -0,0 +1,37 @@ +dropIndex('name_search'); + }); + Schema::table('books', function(Blueprint $table) { + $table->dropIndex('name_search'); + }); + Schema::table('chapters', function(Blueprint $table) { + $table->dropIndex('name_search'); + }); + } +} diff --git a/database/migrations/2015_12_07_195238_add_image_upload_types.php b/database/migrations/2015_12_07_195238_add_image_upload_types.php new file mode 100644 index 000000000..eed937611 --- /dev/null +++ b/database/migrations/2015_12_07_195238_add_image_upload_types.php @@ -0,0 +1,41 @@ +string('path', 400); + $table->string('type')->index(); + }); + + Image::all()->each(function($image) { + $image->path = $image->url; + $image->type = 'gallery'; + $image->save(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('images', function (Blueprint $table) { + $table->dropColumn('type'); + $table->dropColumn('path'); + }); + + } +} diff --git a/database/migrations/2015_12_09_195748_add_user_avatars.php b/database/migrations/2015_12_09_195748_add_user_avatars.php new file mode 100644 index 000000000..47cb027fa --- /dev/null +++ b/database/migrations/2015_12_09_195748_add_user_avatars.php @@ -0,0 +1,31 @@ +integer('image_id')->default(0); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('image_id'); + }); + } +} diff --git a/database/seeds/DummyContentSeeder.php b/database/seeds/DummyContentSeeder.php index d8ab91d22..d2ccc7960 100644 --- a/database/seeds/DummyContentSeeder.php +++ b/database/seeds/DummyContentSeeder.php @@ -23,7 +23,9 @@ class DummyContentSeeder extends Seeder $pages = factory(\BookStack\Page::class, 10)->make(['created_by' => $user->id, 'updated_by' => $user->id, 'book_id' => $book->id]); $chapter->pages()->saveMany($pages); }); + $pages = factory(\BookStack\Page::class, 3)->make(['created_by' => $user->id, 'updated_by' => $user->id]); $book->chapters()->saveMany($chapters); + $book->pages()->saveMany($pages); }); } } diff --git a/gulpfile.js b/gulpfile.js index 621e665be..439618739 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,8 +1,26 @@ 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') .browserify(['jquery-extensions.js', 'global.js'], 'public/js/common.js') - .version(['css/styles.css', 'css/print-styles.css', 'js/common.js']); + .queryVersion(['css/styles.css', 'css/print-styles.css', 'js/common.js']); }); diff --git a/package.json b/package.json index af2cbae58..e33ad170e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "devDependencies": { - "gulp": "^3.8.8", + "gulp": "^3.9.0", "insert-css": "^0.2.0" }, "dependencies": { diff --git a/phpunit.xml b/phpunit.xml index 0884937af..d86aacd00 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -26,5 +26,6 @@ + diff --git a/public/user_avatar.png b/public/user_avatar.png new file mode 100644 index 000000000..26440a9cc Binary files /dev/null and b/public/user_avatar.png differ diff --git a/readme.md b/readme.md index 7b4a20c6d..2d4c84b3b 100644 --- a/readme.md +++ b/readme.md @@ -5,7 +5,7 @@ A platform to create documentation/wiki content. General information about BookS ## Requirements -BookStack has the similar requirements to Laravel. On top of those are some front-end build tools which are only required when developing. +BookStack has similar requirements to Laravel. On top of those are some front-end build tools which are only required when developing. * PHP >= 5.5.9 * OpenSSL PHP Extension @@ -25,11 +25,11 @@ Ensure the requirements are met before installing. This project currently uses the `release` branch of this repository as a stable channel for providing updates. -The installation is currently somewhat complicated. Some PHP/Laravel experience will benefit. +The installation is currently somewhat complicated and will be made simpler in future releases. Some PHP/Laravel experience will currently benefit. 1. Clone the release branch of this repository into a folder. -``` +``` git clone https://github.com/ssddanbrown/BookStack.git --branch release --single-branch ``` @@ -37,7 +37,7 @@ git clone https://github.com/ssddanbrown/BookStack.git --branch release --single 3. Copy the `.env.example` file to `.env` and fill with your own database and mail details. 4. Ensure the `storage` & `bootstrap/cache` folders are writable by the web server. 5. In the application root, Run `php artisan key:generate` to generate a unique application key. -6. If not using apache or `.htaccess` files are disable you will have to create some URL rewrite rules as shown below. +6. If not using apache or if `.htaccess` files are disabled you will have to create some URL rewrite rules as shown below. 7. Run `php migrate` to update the database. 8. Done! You can now login using the default admin details `admin@admin.com` with a password of `password`. It is recommended to change these details directly after first logging in. @@ -76,3 +76,17 @@ Once done you can run `phpunit` in the application root directory to run all tes ## License BookStack is provided under the MIT License. + +## Attribution + +These are the great projects used to help build BookStack: + +* [Laravel](http://laravel.com/) +* [VueJS](http://vuejs.org/) +* [jQuery](https://jquery.com/) +* [TinyMCE](https://www.tinymce.com/) +* [highlight.js](https://highlightjs.org/) +* [jQuery Sortable](https://johnny.github.io/jquery-sortable/) +* [Material Design Iconic Font](http://zavoloklom.github.io/material-design-iconic-font/icons.html) +* [Dropzone.js](http://www.dropzonejs.com/) +* [ZeroClipboard](http://zeroclipboard.org/) diff --git a/resources/assets/js/components/image-manager.vue b/resources/assets/js/components/image-manager.vue index b1c805f8f..dd4a77d57 100644 --- a/resources/assets/js/components/image-manager.vue +++ b/resources/assets/js/components/image-manager.vue @@ -7,7 +7,7 @@
@@ -76,6 +76,13 @@ } }, + props: { + imageType: { + type: String, + required: true + } + }, + created: function () { window.ImageManager = this; }, @@ -88,7 +95,7 @@ methods: { fetchData: function () { var _this = this; - this.$http.get('/images/all/' + _this.page, function (data) { + this.$http.get('/images/' + _this.imageType + '/all/' + _this.page, function (data) { _this.images = _this.images.concat(data.images); _this.hasMore = data.hasMore; _this.page++; @@ -98,7 +105,7 @@ setupDropZone: function () { var _this = this; var dropZone = new Dropzone(_this.$els.dropZone, { - url: '/upload/image', + url: '/images/' + _this.imageType + '/upload', init: function () { var dz = this; this.on("sending", function (file, xhr, data) { @@ -110,8 +117,8 @@ dz.removeFile(file); }); }); - this.on('error', function(file, errorMessage, xhr) { - if(errorMessage.file) { + this.on('error', function (file, errorMessage, xhr) { + if (errorMessage.file) { $(file.previewElement).find('[data-dz-errormessage]').text(errorMessage.file[0]); } console.log(errorMessage); @@ -120,6 +127,10 @@ }); }, + returnCallback: function (image) { + this.callback(image); + }, + imageClick: function (image) { var dblClickTime = 380; var cTime = (new Date()).getTime(); @@ -127,7 +138,7 @@ if (this.cClickTime !== 0 && timeDiff < dblClickTime && this.selectedImage === image) { // DoubleClick if (this.callback) { - this.callback(image); + this.returnCallback(image); } this.hide(); } else { @@ -139,7 +150,7 @@ selectButtonClick: function () { if (this.callback) { - this.callback(this.selectedImage); + this.returnCallback(this.selectedImage); } this.hide(); }, diff --git a/resources/assets/js/components/image-picker.vue b/resources/assets/js/components/image-picker.vue index a52cd3661..7be976caf 100644 --- a/resources/assets/js/components/image-picker.vue +++ b/resources/assets/js/components/image-picker.vue @@ -7,31 +7,89 @@
- | - + | + + diff --git a/resources/views/books/index.blade.php b/resources/views/books/index.blade.php index 32742d126..f28b7c1dd 100644 --- a/resources/views/books/index.blade.php +++ b/resources/views/books/index.blade.php @@ -34,11 +34,22 @@ @endif
+
+ @if($recents) +
 
+

Recently Viewed

+ @include('partials/entity-list', ['entities' => $recents]) + @endif +
 
- @if($recents) -

Recently Viewed

- @include('partials/entity-list', ['entities' => $recents]) - @endif +
diff --git a/resources/views/books/show.blade.php b/resources/views/books/show.blade.php index a9f02aaed..9a965ccbc 100644 --- a/resources/views/books/show.blade.php +++ b/resources/views/books/show.blade.php @@ -58,7 +58,7 @@

Created {{$book->created_at->diffForHumans()}} @if($book->createdBy) by {{$book->createdBy->name}} @endif
- Last Updated {{$book->updated_at->diffForHumans()}} @if($book->createdBy) by {{$book->updatedBy->name}} @endif + Last Updated {{$book->updated_at->diffForHumans()}} @if($book->updatedBy) by {{$book->updatedBy->name}} @endif

diff --git a/resources/views/books/sort-box.blade.php b/resources/views/books/sort-box.blade.php index 768a9f608..2e38f0b0b 100644 --- a/resources/views/books/sort-box.blade.php +++ b/resources/views/books/sort-box.blade.php @@ -2,7 +2,7 @@

{{ $book->name }}

    @foreach($bookChildren as $bookChild) -
  • +
  • {{ $bookChild->name }} @if($bookChild->isA('chapter'))
      diff --git a/resources/views/chapters/show.blade.php b/resources/views/chapters/show.blade.php index 75ae6bf65..9421bbe18 100644 --- a/resources/views/chapters/show.blade.php +++ b/resources/views/chapters/show.blade.php @@ -56,7 +56,7 @@

      Created {{$chapter->created_at->diffForHumans()}} @if($chapter->createdBy) by {{$chapter->createdBy->name}} @endif
      - Last Updated {{$chapter->updated_at->diffForHumans()}} @if($chapter->createdBy) by {{$chapter->updatedBy->name}} @endif + Last Updated {{$chapter->updated_at->diffForHumans()}} @if($chapter->updatedBy) by {{$chapter->updatedBy->name}} @endif

      diff --git a/resources/views/pages/create.blade.php b/resources/views/pages/create.blade.php index 4399de854..6db60a0b9 100644 --- a/resources/views/pages/create.blade.php +++ b/resources/views/pages/create.blade.php @@ -16,5 +16,5 @@ @endif
      - + @stop \ No newline at end of file diff --git a/resources/views/pages/edit.blade.php b/resources/views/pages/edit.blade.php index 7261da8f5..d89daf250 100644 --- a/resources/views/pages/edit.blade.php +++ b/resources/views/pages/edit.blade.php @@ -14,6 +14,6 @@ @include('pages/form', ['model' => $page]) - + @stop \ No newline at end of file diff --git a/resources/views/pages/revisions.blade.php b/resources/views/pages/revisions.blade.php index ae9e3562f..b7ec1f816 100644 --- a/resources/views/pages/revisions.blade.php +++ b/resources/views/pages/revisions.blade.php @@ -32,8 +32,12 @@ @foreach($page->revisions as $revision) {{$revision->name}} - {{$revision->createdBy->name}} - {{$revision->createdBy->name}} + + @if($revision->createdBy) + {{$revision->createdBy->name}} + @endif + + @if($revision->createdBy) {{$revision->createdBy->name}} @else Deleted User @endif {{$revision->created_at->format('jS F, Y H:i:s')}} ({{$revision->created_at->diffForHumans()}}) Preview diff --git a/resources/views/pages/show.blade.php b/resources/views/pages/show.blade.php index 6fc9ff988..d60ee0034 100644 --- a/resources/views/pages/show.blade.php +++ b/resources/views/pages/show.blade.php @@ -7,12 +7,12 @@
      @@ -53,7 +53,7 @@

      Created {{$page->created_at->diffForHumans()}} @if($page->createdBy) by {{$page->createdBy->name}} @endif
      - Last Updated {{$page->updated_at->diffForHumans()}} @if($page->createdBy) by {{$page->updatedBy->name}} @endif + Last Updated {{$page->updated_at->diffForHumans()}} @if($page->updatedBy) by {{$page->updatedBy->name}} @endif

      diff --git a/resources/views/pages/sidebar-tree-list.blade.php b/resources/views/pages/sidebar-tree-list.blade.php index e6c3dfcc6..899ea1e4a 100644 --- a/resources/views/pages/sidebar-tree-list.blade.php +++ b/resources/views/pages/sidebar-tree-list.blade.php @@ -6,8 +6,8 @@ @foreach($sidebarTree as $bookChild) -
    • - +
    • + @if($bookChild->isA('chapter'))@else @endif{{ $bookChild->name }} diff --git a/resources/views/partials/activity-item.blade.php b/resources/views/partials/activity-item.blade.php index 78168ea96..2302eab68 100644 --- a/resources/views/partials/activity-item.blade.php +++ b/resources/views/partials/activity-item.blade.php @@ -10,6 +10,8 @@
      @if($activity->user) {{$activity->user->name}} + @else + A deleted user @endif {{ $activity->getText() }} diff --git a/resources/views/partials/entity-list.blade.php b/resources/views/partials/entity-list.blade.php index 058b0b24f..a357a70fa 100644 --- a/resources/views/partials/entity-list.blade.php +++ b/resources/views/partials/entity-list.blade.php @@ -1,6 +1,6 @@ @if(count($entities) > 0) - @foreach($entities as $entity) + @foreach($entities as $index => $entity) @if($entity->isA('page')) @include('pages/list-item', ['page' => $entity]) @elseif($entity->isA('book')) @@ -8,7 +8,11 @@ @elseif($entity->isA('chapter')) @include('chapters/list-item', ['chapter' => $entity, 'hidePages' => true]) @endif -
      + + @if($index !== count($entities) - 1) +
      + @endif + @endforeach @else

      diff --git a/resources/views/settings/index.blade.php b/resources/views/settings/index.blade.php index eb59e0a5c..d1db1ed33 100644 --- a/resources/views/settings/index.blade.php +++ b/resources/views/settings/index.blade.php @@ -23,12 +23,17 @@

      +
      + +

      For performance reasons, all images are public by default, This option adds a random, hard-to-guess characters in front of image names. Ensure directory indexes are not enabled to prevent easy access.

      + +
    • -

      This image should be 43px in height.

      - +

      This image should be 43px in height.
      Large images will be scaled down.

      +
      @@ -57,7 +62,7 @@
      - +

      If domain restriction is used then email confirmation will be required and the below value will be ignored.

      @@ -81,6 +86,6 @@ - + @stop diff --git a/resources/views/users/edit.blade.php b/resources/views/users/edit.blade.php index 5e28059fe..2a2167279 100644 --- a/resources/views/users/edit.blade.php +++ b/resources/views/users/edit.blade.php @@ -19,26 +19,25 @@
      - +

      Edit {{ $user->id === $currentUser->id ? 'Profile' : 'User' }}

      - - {!! csrf_field() !!} - - @include('users/form', ['model' => $user]) - + {!! csrf_field() !!} + + @include('users/form', ['model' => $user]) +

       

      -
      -

      - {{ $user->name }} -

      -

      You can change your profile picture at Gravatar.

      +
      + +

      This image should be approx 256px square.

      +
      +
      @@ -80,5 +79,5 @@


      - + @stop diff --git a/resources/views/users/form.blade.php b/resources/views/users/form.blade.php index a8aa7e63f..16176bb8d 100644 --- a/resources/views/users/form.blade.php +++ b/resources/views/users/form.blade.php @@ -36,4 +36,5 @@
      Cancel -
      \ No newline at end of file +
      + diff --git a/tests/ActivityTrackingTest.php b/tests/ActivityTrackingTest.php new file mode 100644 index 000000000..8a237f880 --- /dev/null +++ b/tests/ActivityTrackingTest.php @@ -0,0 +1,38 @@ +take(10); + + $this->asAdmin()->visit('/books') + ->dontSeeInElement('#recents', $books[0]->name) + ->dontSeeInElement('#recents', $books[1]->name) + ->visit($books[0]->getUrl()) + ->visit($books[1]->getUrl()) + ->visit('/books') + ->seeInElement('#recents', $books[0]->name) + ->seeInElement('#recents', $books[1]->name); + } + + public function testPopularBooks() + { + $books = \BookStack\Book::all()->take(10); + + $this->asAdmin()->visit('/books') + ->dontSeeInElement('#popular', $books[0]->name) + ->dontSeeInElement('#popular', $books[1]->name) + ->visit($books[0]->getUrl()) + ->visit($books[1]->getUrl()) + ->visit($books[0]->getUrl()) + ->visit('/books') + ->seeInNthElement('#popular .book', 0, $books[0]->name) + ->seeInNthElement('#popular .book', 1, $books[1]->name); + } +} diff --git a/tests/EntityTest.php b/tests/EntityTest.php index 7cc2d640f..07553e7dc 100644 --- a/tests/EntityTest.php +++ b/tests/EntityTest.php @@ -171,4 +171,43 @@ class EntityTest extends TestCase } + public function testEntitiesViewableAfterCreatorDeletion() + { + // Create required assets and revisions + $creator = $this->getNewUser(); + $updater = $this->getNewUser(); + $entities = $this->createEntityChainBelongingToUser($creator, $updater); + $this->actingAs($creator); + app('BookStack\Repos\UserRepo')->destroy($creator); + app('BookStack\Repos\PageRepo')->saveRevision($entities['page']); + + $this->checkEntitiesViewable($entities); + } + + public function testEntitiesViewableAfterUpdaterDeletion() + { + // Create required assets and revisions + $creator = $this->getNewUser(); + $updater = $this->getNewUser(); + $entities = $this->createEntityChainBelongingToUser($creator, $updater); + $this->actingAs($updater); + app('BookStack\Repos\UserRepo')->destroy($updater); + app('BookStack\Repos\PageRepo')->saveRevision($entities['page']); + + $this->checkEntitiesViewable($entities); + } + + private function checkEntitiesViewable($entities) + { + // Check pages and books are visible. + $this->asAdmin(); + $this->visit($entities['book']->getUrl())->seeStatusCode(200) + ->visit($entities['chapter']->getUrl())->seeStatusCode(200) + ->visit($entities['page']->getUrl())->seeStatusCode(200); + // Check revision listing shows no errors. + $this->visit($entities['page']->getUrl()) + ->click('Revisions')->seeStatusCode(200); + } + + } diff --git a/tests/TestCase.php b/tests/TestCase.php index 3f7d846f7..24685321f 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -48,4 +48,65 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase $settings->put($key, $value); } } + + /** + * Create a group of entities that belong to a specific user. + * @param $creatorUser + * @param $updaterUser + * @return array + */ + protected function createEntityChainBelongingToUser($creatorUser, $updaterUser = false) + { + if ($updaterUser === false) $updaterUser = $creatorUser; + $book = factory(BookStack\Book::class)->create(['created_by' => $creatorUser->id, 'updated_by' => $updaterUser->id]); + $chapter = factory(BookStack\Chapter::class)->create(['created_by' => $creatorUser->id, 'updated_by' => $updaterUser->id]); + $page = factory(BookStack\Page::class)->create(['created_by' => $creatorUser->id, 'updated_by' => $updaterUser->id, 'book_id' => $book->id]); + $book->chapters()->saveMany([$chapter]); + $chapter->pages()->saveMany([$page]); + return [ + 'book' => $book, + 'chapter' => $chapter, + 'page' => $page + ]; + } + + /** + * Quick way to create a new user + * @param array $attributes + * @return mixed + */ + protected function getNewUser($attributes = []) + { + $user = factory(\BookStack\User::class)->create($attributes); + $userRepo = app('BookStack\Repos\UserRepo'); + $userRepo->attachDefaultRole($user); + return $user; + } + + /** + * Assert that a given string is seen inside an element. + * + * @param bool|string|null $element + * @param integer $position + * @param string $text + * @param bool $negate + * @return $this + */ + protected function seeInNthElement($element, $position, $text, $negate = false) + { + $method = $negate ? 'assertNotRegExp' : 'assertRegExp'; + + $rawPattern = preg_quote($text, '/'); + + $escapedPattern = preg_quote(e($text), '/'); + + $content = $this->crawler->filter($element)->eq($position)->html(); + + $pattern = $rawPattern == $escapedPattern + ? $rawPattern : "({$rawPattern}|{$escapedPattern})"; + + $this->$method("/$pattern/i", $content); + + return $this; + } }