mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-07-30 04:23:11 +03:00
Added ability to copy a page
In 'More' menu alongside move. Allows you to move if you have permission to create within the new target parent. Closes #673
This commit is contained in:
@ -197,8 +197,8 @@ class Entity extends Ownable
|
||||
* @param $path
|
||||
* @return string
|
||||
*/
|
||||
public function getUrl($path)
|
||||
public function getUrl($path = '/')
|
||||
{
|
||||
return '/';
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
|
@ -592,12 +592,70 @@ class PageController extends Controller
|
||||
return redirect($page->getUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the view to copy a page.
|
||||
* @param string $bookSlug
|
||||
* @param string $pageSlug
|
||||
* @return mixed
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function showCopy($bookSlug, $pageSlug)
|
||||
{
|
||||
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
|
||||
$this->checkOwnablePermission('page-update', $page);
|
||||
session()->flashInput(['name' => $page->name]);
|
||||
return view('pages/copy', [
|
||||
'book' => $page->book,
|
||||
'page' => $page
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a copy of a page within the requested target destination.
|
||||
* @param string $bookSlug
|
||||
* @param string $pageSlug
|
||||
* @param Request $request
|
||||
* @return mixed
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function copy($bookSlug, $pageSlug, Request $request)
|
||||
{
|
||||
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
|
||||
$this->checkOwnablePermission('page-update', $page);
|
||||
|
||||
$entitySelection = $request->get('entity_selection', null);
|
||||
if ($entitySelection === null || $entitySelection === '') {
|
||||
$parent = $page->chapter ? $page->chapter : $page->book;
|
||||
} else {
|
||||
$stringExploded = explode(':', $entitySelection);
|
||||
$entityType = $stringExploded[0];
|
||||
$entityId = intval($stringExploded[1]);
|
||||
|
||||
try {
|
||||
$parent = $this->entityRepo->getById($entityType, $entityId);
|
||||
} catch (\Exception $e) {
|
||||
session()->flash(trans('entities.selected_book_chapter_not_found'));
|
||||
return redirect()->back();
|
||||
}
|
||||
}
|
||||
|
||||
$this->checkOwnablePermission('page-create', $parent);
|
||||
|
||||
$pageCopy = $this->entityRepo->copyPage($page, $parent, $request->get('name', ''));
|
||||
|
||||
Activity::add($pageCopy, 'page_create', $pageCopy->book->id);
|
||||
session()->flash('success', trans('entities.pages_copy_success'));
|
||||
|
||||
return redirect($pageCopy->getUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the permissions for this page.
|
||||
* @param string $bookSlug
|
||||
* @param string $pageSlug
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function restrict($bookSlug, $pageSlug, Request $request)
|
||||
{
|
||||
|
@ -89,16 +89,17 @@ class SearchController extends Controller
|
||||
{
|
||||
$entityTypes = $request->filled('types') ? collect(explode(',', $request->get('types'))) : collect(['page', 'chapter', 'book']);
|
||||
$searchTerm = $request->get('term', false);
|
||||
$permission = $request->get('permission', 'view');
|
||||
|
||||
// Search for entities otherwise show most popular
|
||||
if ($searchTerm !== false) {
|
||||
$searchTerm .= ' {type:'. implode('|', $entityTypes->toArray()) .'}';
|
||||
$entities = $this->searchService->searchEntities($searchTerm)['results'];
|
||||
$entities = $this->searchService->searchEntities($searchTerm, 'all', 1, 20, $permission)['results'];
|
||||
} else {
|
||||
$entityNames = $entityTypes->map(function ($type) {
|
||||
return 'BookStack\\' . ucfirst($type);
|
||||
})->toArray();
|
||||
$entities = $this->viewService->getPopular(20, 0, $entityNames);
|
||||
$entities = $this->viewService->getPopular(20, 0, $entityNames, $permission);
|
||||
}
|
||||
|
||||
return view('search/entity-ajax-list', ['entities' => $entities]);
|
||||
|
@ -593,6 +593,30 @@ class EntityRepo
|
||||
return $slug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new draft page instance.
|
||||
* @param Book $book
|
||||
* @param Chapter|bool $chapter
|
||||
* @return Page
|
||||
*/
|
||||
public function getDraftPage(Book $book, $chapter = false)
|
||||
{
|
||||
$page = $this->page->newInstance();
|
||||
$page->name = trans('entities.pages_initial_name');
|
||||
$page->created_by = user()->id;
|
||||
$page->updated_by = user()->id;
|
||||
$page->draft = true;
|
||||
|
||||
if ($chapter) {
|
||||
$page->chapter_id = $chapter->id;
|
||||
}
|
||||
|
||||
$book->pages()->save($page);
|
||||
$page = $this->page->find($page->id);
|
||||
$this->permissionService->buildJointPermissionsForEntity($page);
|
||||
return $page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish a draft page to make it a normal page.
|
||||
* Sets the slug and updates the content.
|
||||
@ -621,6 +645,43 @@ class EntityRepo
|
||||
return $draftPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a copy of a page in a new location with a new name.
|
||||
* @param Page $page
|
||||
* @param Entity $newParent
|
||||
* @param string $newName
|
||||
* @return Page
|
||||
*/
|
||||
public function copyPage(Page $page, Entity $newParent, $newName = '')
|
||||
{
|
||||
$newBook = $newParent->isA('book') ? $newParent : $newParent->book;
|
||||
$newChapter = $newParent->isA('chapter') ? $newParent : null;
|
||||
$copyPage = $this->getDraftPage($newBook, $newChapter);
|
||||
$pageData = $page->getAttributes();
|
||||
|
||||
// Update name
|
||||
if (!empty($newName)) {
|
||||
$pageData['name'] = $newName;
|
||||
}
|
||||
|
||||
// Copy tags from previous page if set
|
||||
if ($page->tags) {
|
||||
$pageData['tags'] = [];
|
||||
foreach ($page->tags as $tag) {
|
||||
$pageData['tags'][] = ['name' => $tag->name, 'value' => $tag->value];
|
||||
}
|
||||
}
|
||||
|
||||
// Set priority
|
||||
if ($newParent->isA('chapter')) {
|
||||
$pageData['priority'] = $this->getNewChapterPriority($newParent);
|
||||
} else {
|
||||
$pageData['priority'] = $this->getNewBookPriority($newParent);
|
||||
}
|
||||
|
||||
return $this->publishPageDraft($copyPage, $pageData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a page revision into the system.
|
||||
* @param Page $page
|
||||
@ -805,30 +866,6 @@ class EntityRepo
|
||||
return strip_tags($html);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new draft page instance.
|
||||
* @param Book $book
|
||||
* @param Chapter|bool $chapter
|
||||
* @return Page
|
||||
*/
|
||||
public function getDraftPage(Book $book, $chapter = false)
|
||||
{
|
||||
$page = $this->page->newInstance();
|
||||
$page->name = trans('entities.pages_initial_name');
|
||||
$page->created_by = user()->id;
|
||||
$page->updated_by = user()->id;
|
||||
$page->draft = true;
|
||||
|
||||
if ($chapter) {
|
||||
$page->chapter_id = $chapter->id;
|
||||
}
|
||||
|
||||
$book->pages()->save($page);
|
||||
$page = $this->page->find($page->id);
|
||||
$this->permissionService->buildJointPermissionsForEntity($page);
|
||||
return $page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for image usage within page content.
|
||||
* @param $imageString
|
||||
|
@ -630,16 +630,17 @@ class PermissionService
|
||||
* @param string $tableName
|
||||
* @param string $entityIdColumn
|
||||
* @param string $entityTypeColumn
|
||||
* @param string $action
|
||||
* @return mixed
|
||||
*/
|
||||
public function filterRestrictedEntityRelations($query, $tableName, $entityIdColumn, $entityTypeColumn)
|
||||
public function filterRestrictedEntityRelations($query, $tableName, $entityIdColumn, $entityTypeColumn, $action = 'view')
|
||||
{
|
||||
if ($this->isAdmin()) {
|
||||
$this->clean();
|
||||
return $query;
|
||||
}
|
||||
|
||||
$this->currentAction = 'view';
|
||||
$this->currentAction = $action;
|
||||
$tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn, 'entityTypeColumn' => $entityTypeColumn];
|
||||
|
||||
$q = $query->where(function ($query) use ($tableDetails) {
|
||||
|
@ -67,7 +67,7 @@ class SearchService
|
||||
* @param int $count - Count of each entity to search, Total returned could can be larger and not guaranteed.
|
||||
* @return array[int, Collection];
|
||||
*/
|
||||
public function searchEntities($searchString, $entityType = 'all', $page = 1, $count = 20)
|
||||
public function searchEntities($searchString, $entityType = 'all', $page = 1, $count = 20, $action = 'view')
|
||||
{
|
||||
$terms = $this->parseSearchString($searchString);
|
||||
$entityTypes = array_keys($this->entities);
|
||||
@ -87,8 +87,8 @@ class SearchService
|
||||
if (!in_array($entityType, $entityTypes)) {
|
||||
continue;
|
||||
}
|
||||
$search = $this->searchEntityTable($terms, $entityType, $page, $count);
|
||||
$entityTotal = $this->searchEntityTable($terms, $entityType, $page, $count, true);
|
||||
$search = $this->searchEntityTable($terms, $entityType, $page, $count, $action);
|
||||
$entityTotal = $this->searchEntityTable($terms, $entityType, $page, $count, $action, true);
|
||||
if ($entityTotal > $page * $count) {
|
||||
$hasMore = true;
|
||||
}
|
||||
@ -147,12 +147,13 @@ class SearchService
|
||||
* @param string $entityType
|
||||
* @param int $page
|
||||
* @param int $count
|
||||
* @param string $action
|
||||
* @param bool $getCount Return the total count of the search
|
||||
* @return \Illuminate\Database\Eloquent\Collection|int|static[]
|
||||
*/
|
||||
public function searchEntityTable($terms, $entityType = 'page', $page = 1, $count = 20, $getCount = false)
|
||||
public function searchEntityTable($terms, $entityType = 'page', $page = 1, $count = 20, $action = 'view', $getCount = false)
|
||||
{
|
||||
$query = $this->buildEntitySearchQuery($terms, $entityType);
|
||||
$query = $this->buildEntitySearchQuery($terms, $entityType, $action);
|
||||
if ($getCount) {
|
||||
return $query->count();
|
||||
}
|
||||
@ -165,9 +166,10 @@ class SearchService
|
||||
* Create a search query for an entity
|
||||
* @param array $terms
|
||||
* @param string $entityType
|
||||
* @param string $action
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
protected function buildEntitySearchQuery($terms, $entityType = 'page')
|
||||
protected function buildEntitySearchQuery($terms, $entityType = 'page', $action = 'view')
|
||||
{
|
||||
$entity = $this->getEntity($entityType);
|
||||
$entitySelect = $entity->newQuery();
|
||||
@ -212,7 +214,7 @@ class SearchService
|
||||
}
|
||||
}
|
||||
|
||||
return $this->permissionService->enforceEntityRestrictions($entityType, $entitySelect, 'view');
|
||||
return $this->permissionService->enforceEntityRestrictions($entityType, $entitySelect, $action);
|
||||
}
|
||||
|
||||
|
||||
|
@ -51,11 +51,13 @@ class ViewService
|
||||
* @param int $count
|
||||
* @param int $page
|
||||
* @param bool|false|array $filterModel
|
||||
* @param string $action - used for permission checking
|
||||
* @return
|
||||
*/
|
||||
public function getPopular($count = 10, $page = 0, $filterModel = false)
|
||||
public function getPopular($count = 10, $page = 0, $filterModel = false, $action = 'view')
|
||||
{
|
||||
$skipCount = $count * $page;
|
||||
$query = $this->permissionService->filterRestrictedEntityRelations($this->view, 'views', 'viewable_id', 'viewable_type')
|
||||
$query = $this->permissionService->filterRestrictedEntityRelations($this->view, 'views', 'viewable_id', 'viewable_type', $action)
|
||||
->select('*', 'viewable_id', 'viewable_type', \DB::raw('SUM(views) as view_count'))
|
||||
->groupBy('viewable_id', 'viewable_type')
|
||||
->orderBy('view_count', 'desc');
|
||||
|
Reference in New Issue
Block a user