mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-04-10 17:01:42 +08:00
Started widening of activity logging
In progress, Need to implement much of the logging in controllers. Also cleaned up base controller along the way.
This commit is contained in:
parent
712ccd23c4
commit
3f7180fa99
@ -5,6 +5,7 @@ use BookStack\Auth\User;
|
||||
use BookStack\Entities\Chapter;
|
||||
use BookStack\Entities\Entity;
|
||||
use BookStack\Entities\Page;
|
||||
use BookStack\Interfaces\Loggable;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
@ -30,6 +31,22 @@ class ActivityService
|
||||
$this->setNotification($type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a generic activity event to the database.
|
||||
* @param string|Loggable $detail
|
||||
*/
|
||||
public function add(string $type, $detail = '')
|
||||
{
|
||||
if ($detail instanceof Loggable) {
|
||||
$detail = $detail->logDescriptor();
|
||||
}
|
||||
|
||||
$activity = $this->newActivityForUser($type);
|
||||
$activity->detail = $detail;
|
||||
$activity->save();
|
||||
$this->setNotification($type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new activity instance for the current user.
|
||||
*/
|
||||
|
@ -7,16 +7,46 @@ class ActivityType
|
||||
const PAGE_DELETE = 'page_delete';
|
||||
const PAGE_RESTORE = 'page_restore';
|
||||
const PAGE_MOVE = 'page_move';
|
||||
const COMMENTED_ON = 'commented_on';
|
||||
|
||||
const CHAPTER_CREATE = 'chapter_create';
|
||||
const CHAPTER_UPDATE = 'chapter_update';
|
||||
const CHAPTER_DELETE = 'chapter_delete';
|
||||
const CHAPTER_MOVE = 'chapter_move';
|
||||
|
||||
const BOOK_CREATE = 'book_create';
|
||||
const BOOK_UPDATE = 'book_update';
|
||||
const BOOK_DELETE = 'book_delete';
|
||||
const BOOK_SORT = 'book_sort';
|
||||
|
||||
const BOOKSHELF_CREATE = 'bookshelf_create';
|
||||
const BOOKSHELF_UPDATE = 'bookshelf_update';
|
||||
const BOOKSHELF_DELETE = 'bookshelf_delete';
|
||||
|
||||
const COMMENTED_ON = 'commented_on';
|
||||
const PERMISSIONS_UPDATE = 'permissions_update';
|
||||
|
||||
const SETTINGS_UPDATE = 'settings_update';
|
||||
const MAINTENANCE_ACTION_RUN = 'maintenance_action_run';
|
||||
|
||||
const RECYCLE_BIN_EMPTY = 'recycle_bin_empty';
|
||||
const RECYCLE_BIN_RESTORE = 'recycle_bin_restore';
|
||||
const RECYCLE_BIN_DESTROY = 'recycle_bin_destroy';
|
||||
|
||||
// TODO - Implement all below
|
||||
const USER_CREATE = 'user_create';
|
||||
const USER_UPDATE = 'user_update';
|
||||
const USER_DELETE = 'user_delete';
|
||||
|
||||
const API_TOKEN_CREATE = 'api_token_create';
|
||||
const API_TOKEN_UPDATE = 'api_token_update';
|
||||
const API_TOKEN_DELETE = 'api_token_delete';
|
||||
|
||||
const ROLE_CREATE = 'role_create';
|
||||
const ROLE_UPDATE = 'role_update';
|
||||
const ROLE_DELETE = 'role_delete';
|
||||
|
||||
const ACCESS_PASSWORD_RESET = 'access_password_reset_request';
|
||||
const ACCESS_PASSWORD_RESET_UPDATE = 'access_password_reset_update';
|
||||
const ACCESS_LOGIN = 'access_login';
|
||||
const ACCESS_FAILED_LOGIN = 'access_failed_login';
|
||||
}
|
@ -1,11 +1,12 @@
|
||||
<?php namespace BookStack\Entities;
|
||||
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Interfaces\Loggable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
|
||||
class Deletion extends Model
|
||||
class Deletion extends Model implements Loggable
|
||||
{
|
||||
|
||||
/**
|
||||
@ -38,4 +39,9 @@ class Deletion extends Model
|
||||
return $record;
|
||||
}
|
||||
|
||||
public function logDescriptor(): string
|
||||
{
|
||||
$deletable = $this->deletable()->first();
|
||||
return "Deletion ({$this->id}) for {$deletable->getType()} ({$deletable->id}) {$deletable->name}";
|
||||
}
|
||||
}
|
||||
|
@ -2,11 +2,12 @@
|
||||
|
||||
namespace BookStack\Entities\Repos;
|
||||
|
||||
use BookStack\Actions\ActivityType;
|
||||
use BookStack\Actions\TagRepo;
|
||||
use BookStack\Entities\Book;
|
||||
use BookStack\Entities\Entity;
|
||||
use BookStack\Entities\HasCoverImage;
|
||||
use BookStack\Exceptions\ImageUploadException;
|
||||
use BookStack\Facades\Activity;
|
||||
use BookStack\Uploads\ImageRepo;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Collection;
|
||||
@ -18,10 +19,6 @@ class BaseRepo
|
||||
protected $imageRepo;
|
||||
|
||||
|
||||
/**
|
||||
* BaseRepo constructor.
|
||||
* @param $tagRepo
|
||||
*/
|
||||
public function __construct(TagRepo $tagRepo, ImageRepo $imageRepo)
|
||||
{
|
||||
$this->tagRepo = $tagRepo;
|
||||
@ -115,5 +112,6 @@ class BaseRepo
|
||||
|
||||
$entity->save();
|
||||
$entity->rebuildPermissions();
|
||||
Activity::addForEntity($entity, ActivityType::PERMISSIONS_UPDATE);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
<?php namespace BookStack\Http\Controllers\Api;
|
||||
|
||||
use BookStack\Actions\ActivityType;
|
||||
use BookStack\Facades\Activity;
|
||||
use BookStack\Entities\Repos\BookshelfRepo;
|
||||
use BookStack\Entities\Bookshelf;
|
||||
use Exception;
|
||||
|
@ -2,21 +2,20 @@
|
||||
|
||||
namespace BookStack\Http\Controllers;
|
||||
|
||||
use BookStack\Facades\Activity;
|
||||
use BookStack\Interfaces\Loggable;
|
||||
use BookStack\Ownable;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Http\Exceptions\HttpResponseException;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
abstract class Controller extends BaseController
|
||||
{
|
||||
use DispatchesJobs, ValidatesRequests;
|
||||
|
||||
/**
|
||||
* Controller constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
@ -43,9 +42,8 @@ abstract class Controller extends BaseController
|
||||
|
||||
/**
|
||||
* Adds the page title into the view.
|
||||
* @param $title
|
||||
*/
|
||||
public function setPageTitle($title)
|
||||
public function setPageTitle(string $title)
|
||||
{
|
||||
view()->share('pageTitle', $title);
|
||||
}
|
||||
@ -67,79 +65,59 @@ abstract class Controller extends BaseController
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for a permission.
|
||||
* @param string $permissionName
|
||||
* @return bool|\Illuminate\Http\RedirectResponse
|
||||
* Checks that the current user has the given permission otherwise throw an exception.
|
||||
*/
|
||||
protected function checkPermission($permissionName)
|
||||
protected function checkPermission(string $permission): void
|
||||
{
|
||||
if (!user() || !user()->can($permissionName)) {
|
||||
if (!user() || !user()->can($permission)) {
|
||||
$this->showPermissionError();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the current user's permissions against an ownable item.
|
||||
* @param $permission
|
||||
* @param Ownable $ownable
|
||||
* @return bool
|
||||
* Check the current user's permissions against an ownable item otherwise throw an exception.
|
||||
*/
|
||||
protected function checkOwnablePermission($permission, Ownable $ownable)
|
||||
protected function checkOwnablePermission(string $permission, Ownable $ownable): void
|
||||
{
|
||||
if (userCan($permission, $ownable)) {
|
||||
return true;
|
||||
if (!userCan($permission, $ownable)) {
|
||||
$this->showPermissionError();
|
||||
}
|
||||
return $this->showPermissionError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a user has a permission or bypass if the callback is true.
|
||||
* @param $permissionName
|
||||
* @param $callback
|
||||
* @return bool
|
||||
* Check if a user has a permission or bypass the permission
|
||||
* check if the given callback resolves true.
|
||||
*/
|
||||
protected function checkPermissionOr($permissionName, $callback)
|
||||
protected function checkPermissionOr(string $permission, callable $callback): void
|
||||
{
|
||||
$callbackResult = $callback();
|
||||
if ($callbackResult === false) {
|
||||
$this->checkPermission($permissionName);
|
||||
if ($callback() !== true) {
|
||||
$this->checkPermission($permission);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current user has a permission or bypass if the provided user
|
||||
* id matches the current user.
|
||||
* @param string $permissionName
|
||||
* @param int $userId
|
||||
* @return bool
|
||||
*/
|
||||
protected function checkPermissionOrCurrentUser(string $permissionName, int $userId)
|
||||
protected function checkPermissionOrCurrentUser(string $permission, int $userId): void
|
||||
{
|
||||
return $this->checkPermissionOr($permissionName, function () use ($userId) {
|
||||
$this->checkPermissionOr($permission, function () use ($userId) {
|
||||
return $userId === user()->id;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send back a json error message.
|
||||
* @param string $messageText
|
||||
* @param int $statusCode
|
||||
* @return mixed
|
||||
*/
|
||||
protected function jsonError($messageText = "", $statusCode = 500)
|
||||
protected function jsonError(string $messageText = "", int $statusCode = 500): JsonResponse
|
||||
{
|
||||
return response()->json(['message' => $messageText, 'status' => 'error'], $statusCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a response that forces a download in the browser.
|
||||
* @param string $content
|
||||
* @param string $fileName
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
protected function downloadResponse(string $content, string $fileName)
|
||||
protected function downloadResponse(string $content, string $fileName): Response
|
||||
{
|
||||
return response()->make($content, 200, [
|
||||
'Content-Type' => 'application/octet-stream',
|
||||
@ -149,31 +127,37 @@ abstract class Controller extends BaseController
|
||||
|
||||
/**
|
||||
* Show a positive, successful notification to the user on next view load.
|
||||
* @param string $message
|
||||
*/
|
||||
protected function showSuccessNotification(string $message)
|
||||
protected function showSuccessNotification(string $message): void
|
||||
{
|
||||
session()->flash('success', $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a warning notification to the user on next view load.
|
||||
* @param string $message
|
||||
*/
|
||||
protected function showWarningNotification(string $message)
|
||||
protected function showWarningNotification(string $message): void
|
||||
{
|
||||
session()->flash('warning', $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show an error notification to the user on next view load.
|
||||
* @param string $message
|
||||
*/
|
||||
protected function showErrorNotification(string $message)
|
||||
protected function showErrorNotification(string $message): void
|
||||
{
|
||||
session()->flash('error', $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an activity in the system.
|
||||
* @param string|Loggable
|
||||
*/
|
||||
protected function logActivity(string $type, $detail = ''): void
|
||||
{
|
||||
Activity::add($type, $detail);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules for image files.
|
||||
*/
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace BookStack\Http\Controllers;
|
||||
|
||||
use BookStack\Actions\ActivityType;
|
||||
use BookStack\Entities\Managers\TrashCan;
|
||||
use BookStack\Notifications\TestEmail;
|
||||
use BookStack\Uploads\ImageService;
|
||||
@ -35,6 +36,7 @@ class MaintenanceController extends Controller
|
||||
public function cleanupImages(Request $request, ImageService $imageService)
|
||||
{
|
||||
$this->checkPermission('settings-manage');
|
||||
$this->logActivity(ActivityType::MAINTENANCE_ACTION_RUN, 'cleanup-images');
|
||||
|
||||
$checkRevisions = !($request->get('ignore_revisions', 'false') === 'true');
|
||||
$dryRun = !($request->has('confirm'));
|
||||
@ -61,6 +63,7 @@ class MaintenanceController extends Controller
|
||||
public function sendTestEmail()
|
||||
{
|
||||
$this->checkPermission('settings-manage');
|
||||
$this->logActivity(ActivityType::MAINTENANCE_ACTION_RUN, 'send-test-email');
|
||||
|
||||
try {
|
||||
user()->notify(new TestEmail());
|
||||
|
@ -1,5 +1,6 @@
|
||||
<?php namespace BookStack\Http\Controllers;
|
||||
|
||||
use BookStack\Actions\ActivityType;
|
||||
use BookStack\Entities\Deletion;
|
||||
use BookStack\Entities\Managers\TrashCan;
|
||||
|
||||
@ -56,6 +57,7 @@ class RecycleBinController extends Controller
|
||||
{
|
||||
/** @var Deletion $deletion */
|
||||
$deletion = Deletion::query()->findOrFail($id);
|
||||
$this->logActivity(ActivityType::RECYCLE_BIN_RESTORE, $deletion);
|
||||
$restoreCount = (new TrashCan())->restoreFromDeletion($deletion);
|
||||
|
||||
$this->showSuccessNotification(trans('settings.recycle_bin_restore_notification', ['count' => $restoreCount]));
|
||||
@ -85,6 +87,7 @@ class RecycleBinController extends Controller
|
||||
$deletion = Deletion::query()->findOrFail($id);
|
||||
$deleteCount = (new TrashCan())->destroyFromDeletion($deletion);
|
||||
|
||||
$this->logActivity(ActivityType::RECYCLE_BIN_DESTROY, $deletion);
|
||||
$this->showSuccessNotification(trans('settings.recycle_bin_destroy_notification', ['count' => $deleteCount]));
|
||||
return redirect($this->recycleBinBaseUrl);
|
||||
}
|
||||
@ -97,6 +100,7 @@ class RecycleBinController extends Controller
|
||||
{
|
||||
$deleteCount = (new TrashCan())->empty();
|
||||
|
||||
$this->logActivity(ActivityType::RECYCLE_BIN_EMPTY);
|
||||
$this->showSuccessNotification(trans('settings.recycle_bin_destroy_notification', ['count' => $deleteCount]));
|
||||
return redirect($this->recycleBinBaseUrl);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
<?php namespace BookStack\Http\Controllers;
|
||||
|
||||
use BookStack\Actions\ActivityType;
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Uploads\ImageRepo;
|
||||
use Illuminate\Http\Request;
|
||||
@ -47,10 +48,10 @@ class SettingController extends Controller
|
||||
|
||||
// Cycles through posted settings and update them
|
||||
foreach ($request->all() as $name => $value) {
|
||||
$key = str_replace('setting-', '', trim($name));
|
||||
if (strpos($name, 'setting-') !== 0) {
|
||||
continue;
|
||||
}
|
||||
$key = str_replace('setting-', '', trim($name));
|
||||
setting()->put($key, $value);
|
||||
}
|
||||
|
||||
@ -68,8 +69,10 @@ class SettingController extends Controller
|
||||
setting()->remove('app-logo');
|
||||
}
|
||||
|
||||
$section = $request->get('section', '');
|
||||
$this->logActivity(ActivityType::SETTINGS_UPDATE, $section);
|
||||
$this->showSuccessNotification(trans('settings.settings_save_success'));
|
||||
$redirectLocation = '/settings#' . $request->get('section', '');
|
||||
$redirectLocation = '/settings#' . $section;
|
||||
return redirect(rtrim($redirectLocation, '#'));
|
||||
}
|
||||
}
|
||||
|
11
app/Interfaces/Loggable.php
Normal file
11
app/Interfaces/Loggable.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Interfaces;
|
||||
|
||||
interface Loggable
|
||||
{
|
||||
/**
|
||||
* Get the string descriptor for this item.
|
||||
*/
|
||||
public function logDescriptor(): string;
|
||||
}
|
@ -45,4 +45,5 @@ return [
|
||||
|
||||
// Other
|
||||
'commented_on' => 'commented on',
|
||||
'permissions_update' => 'updated permissions',
|
||||
];
|
||||
|
Loading…
x
Reference in New Issue
Block a user