]> BookStack Code Mirror - bookstack/commitdiff
Merge branch 'master' into 2019-design
authorDan Brown <redacted>
Sun, 13 Jan 2019 14:10:27 +0000 (14:10 +0000)
committerDan Brown <redacted>
Sun, 13 Jan 2019 14:10:27 +0000 (14:10 +0000)
1  2 
app/Entities/Repos/EntityRepo.php
app/Http/Controllers/UserController.php
resources/lang/en/common.php
resources/views/common/header.blade.php
resources/views/pages/show.blade.php

index 13a9d9a9c33ec3097a71560efb1df70e8bd97d73,576ed70f00bdedb45597da098cf85e5c0c15a902..15340c90693706340234ab7b959a1cfbaab41d1f
@@@ -15,7 -15,6 +15,7 @@@ use BookStack\Exceptions\NotFoundExcept
  use BookStack\Exceptions\NotifyException;
  use BookStack\Uploads\AttachmentService;
  use DOMDocument;
 +use Illuminate\Database\Eloquent\Builder;
  use Illuminate\Http\Request;
  use Illuminate\Support\Collection;
  
@@@ -180,27 -179,11 +180,27 @@@ class EntityRep
       * Get all entities in a paginated format
       * @param $type
       * @param int $count
 +     * @param string $sort
 +     * @param string $order
       * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
       */
 -    public function getAllPaginated($type, $count = 10)
 +    public function getAllPaginated($type, int $count = 10, string $sort = 'name', string $order = 'asc')
      {
 -        return $this->entityQuery($type)->orderBy('name', 'asc')->paginate($count);
 +        $query = $this->entityQuery($type);
 +        $query = $this->addSortToQuery($query, $sort, $order);
 +        return $query->paginate($count);
 +    }
 +
 +    protected function addSortToQuery(Builder $query, string $sort = 'name', string $order = 'asc')
 +    {
 +        $order = ($order === 'asc') ? 'asc' : 'desc';
 +        $propertySorts = ['name', 'created_at', 'updated_at'];
 +
 +        if (in_array($sort, $propertySorts)) {
 +            return $query->orderBy($sort, $order);
 +        }
 +
 +        return $query;
      }
  
      /**
      }
  
      /**
-      * Render the page for viewing, Parsing and performing features such as page transclusion.
+      * Render the page for viewing
       * @param Page $page
-      * @param bool $ignorePermissions
-      * @return mixed|string
+      * @param bool $blankIncludes
+      * @return string
       */
-     public function renderPage(Page $page, $ignorePermissions = false)
+     public function renderPage(Page $page, bool $blankIncludes = false) : string
      {
          $content = $page->html;
          if (!config('app.allow_content_scripts')) {
              $content = $this->escapeScripts($content);
          }
  
-         $matches = [];
-         preg_match_all("/{{@\s?([0-9].*?)}}/", $content, $matches);
-         if (count($matches[0]) === 0) {
-             return $content;
+         if ($blankIncludes) {
+             $content = $this->blankPageIncludes($content);
+         } else {
+             $content = $this->parsePageIncludes($content);
          }
  
+         return $content;
+     }
+     /**
+      * Remove any page include tags within the given HTML.
+      * @param string $html
+      * @return string
+      */
+     protected function blankPageIncludes(string $html) : string
+     {
+         return preg_replace("/{{@\s?([0-9].*?)}}/", '', $html);
+     }
+     /**
+      * Parse any include tags "{{@<page_id>#section}}" to be part of the page.
+      * @param string $html
+      * @return mixed|string
+      */
+     protected function parsePageIncludes(string $html) : string
+     {
+         $matches = [];
+         preg_match_all("/{{@\s?([0-9].*?)}}/", $html, $matches);
          $topLevelTags = ['table', 'ul', 'ol'];
          foreach ($matches[1] as $index => $includeId) {
              $splitInclude = explode('#', $includeId, 2);
                  continue;
              }
  
-             $matchedPage = $this->getById('page', $pageId, false, $ignorePermissions);
+             $matchedPage = $this->getById('page', $pageId);
              if ($matchedPage === null) {
-                 $content = str_replace($matches[0][$index], '', $content);
+                 $html = str_replace($matches[0][$index], '', $html);
                  continue;
              }
  
              if (count($splitInclude) === 1) {
-                 $content = str_replace($matches[0][$index], $matchedPage->html, $content);
+                 $html = str_replace($matches[0][$index], $matchedPage->html, $html);
                  continue;
              }
  
              $doc->loadHTML(mb_convert_encoding('<body>'.$matchedPage->html.'</body>', 'HTML-ENTITIES', 'UTF-8'));
              $matchingElem = $doc->getElementById($splitInclude[1]);
              if ($matchingElem === null) {
-                 $content = str_replace($matches[0][$index], '', $content);
+                 $html = str_replace($matches[0][$index], '', $html);
                  continue;
              }
              $innerContent = '';
                      $innerContent .= $doc->saveHTML($childNode);
                  }
              }
-             $content = str_replace($matches[0][$index], trim($innerContent), $content);
+             $html = str_replace($matches[0][$index], trim($innerContent), $html);
          }
  
-         return $content;
+         return $html;
      }
  
      /**
       * Escape script tags within HTML content.
       * @param string $html
-      * @return mixed
+      * @return string
       */
-     protected function escapeScripts(string $html)
+     protected function escapeScripts(string $html) : string
      {
          $scriptSearchRegex = '/<script.*?>.*?<\/script>/ms';
          $matches = [];
          preg_match_all($scriptSearchRegex, $html, $matches);
-         if (count($matches) === 0) {
-             return $html;
-         }
  
          foreach ($matches[0] as $match) {
              $html = str_replace($match, htmlentities($match), $html);
index 92dc3cdaaa9d00f478c46e72a06b70c9f03d3579,cc5ada3f283d2bd4e24f01cc9235e637b0ffa9a1..933b3d59411667d6eeb47e6c7550cfd65e276b77
@@@ -3,6 -3,7 +3,7 @@@
  use BookStack\Auth\Access\SocialAuthService;
  use BookStack\Auth\User;
  use BookStack\Auth\UserRepo;
+ use BookStack\Exceptions\UserUpdateException;
  use Illuminate\Http\Request;
  use Illuminate\Http\Response;
  
@@@ -15,7 -16,7 +16,7 @@@ class UserController extends Controlle
      /**
       * UserController constructor.
       * @param User     $user
-      * @param \BookStack\Auth\UserRepo $userRepo
+      * @param UserRepo $userRepo
       */
      public function __construct(User $user, UserRepo $userRepo)
      {
@@@ -59,6 -60,7 +60,7 @@@
       * Store a newly created user in storage.
       * @param  Request $request
       * @return Response
+      * @throws UserUpdateException
       */
      public function store(Request $request)
      {
  
          if ($request->filled('roles')) {
              $roles = $request->get('roles');
-             $user->roles()->sync($roles);
+             $this->userRepo->setUserRoles($user, $roles);
          }
  
-         $this->userRepo->downloadGravatarToUserAvatar($user);
+         $this->userRepo->downloadAndAssignUserAvatar($user);
  
          return redirect('/settings/users');
      }
      /**
       * Update the specified user in storage.
       * @param  Request $request
-      * @param  int     $id
+      * @param  int $id
       * @return Response
+      * @throws UserUpdateException
       */
      public function update(Request $request, $id)
      {
              'setting'          => 'array'
          ]);
  
-         $user = $this->user->findOrFail($id);
+         $user = $this->userRepo->getById($id);
          $user->fill($request->all());
  
          // Role updates
          if (userCan('users-manage') && $request->filled('roles')) {
              $roles = $request->get('roles');
-             $user->roles()->sync($roles);
+             $this->userRepo->setUserRoles($user, $roles);
          }
  
          // Password updates
              return $this->currentUser->id == $id;
          });
  
-         $user = $this->user->findOrFail($id);
+         $user = $this->userRepo->getById($id);
          $this->setPageTitle(trans('settings.users_delete_named', ['userName' => $user->name]));
          return view('users/delete', ['user' => $user]);
      }
       * Remove the specified user from storage.
       * @param  int $id
       * @return Response
+      * @throws \Exception
       */
      public function destroy($id)
      {
       */
      public function switchBookView($id, Request $request)
      {
 -        $this->checkPermissionOr('users-manage', function () use ($id) {
 -            return $this->currentUser->id == $id;
 -        });
 +        return $this->switchViewType($id, $request, 'books');
 +    }
 +
 +    /**
 +     * Update the user's preferred shelf-list display setting.
 +     * @param $id
 +     * @param Request $request
 +     * @return \Illuminate\Http\RedirectResponse
 +     */
 +    public function switchShelfView($id, Request $request)
 +    {
 +        return $this->switchViewType($id, $request, 'bookshelves');
 +    }
 +
 +    /**
 +     * For a type of list, switch with stored view type for a user.
 +     * @param integer $userId
 +     * @param Request $request
 +     * @param string $listName
 +     * @return \Illuminate\Http\RedirectResponse
 +     */
 +    protected function switchViewType($userId, Request $request, string $listName)
 +    {
 +        $this->checkPermissionOrCurrentUser('users-manage', $userId);
  
          $viewType = $request->get('view_type');
          if (!in_array($viewType, ['grid', 'list'])) {
              $viewType = 'list';
          }
  
-         $user = $this->user->findOrFail($userId);
 -        $user = $this->user->findOrFail($id);
 -        setting()->putUser($user, 'books_view_type', $viewType);
++        $user = $this->userRepo->getById($id);
 +        $key = $listName . '_view_type';
 +        setting()->putUser($user, $key, $viewType);
  
 -        return redirect()->back(302, [], "/settings/users/$id");
 +        return redirect()->back(302, [], "/settings/users/$userId");
      }
  
      /**
 -     * Update the user's preferred shelf-list display setting.
 +     * Change the stored sort type for the books view.
       * @param $id
       * @param Request $request
       * @return \Illuminate\Http\RedirectResponse
       */
 -    public function switchShelfView($id, Request $request)
 +    public function changeBooksSort($id, Request $request)
      {
 -        $this->checkPermissionOr('users-manage', function () use ($id) {
 -            return $this->currentUser->id == $id;
 -        });
 +        // TODO - Test this endpoint
 +        return $this->changeListSort($id, $request, 'books');
 +    }
  
 -        $viewType = $request->get('view_type');
 -        if (!in_array($viewType, ['grid', 'list'])) {
 -            $viewType = 'list';
 +    /**
 +     * Changed the stored preference for a list sort order.
 +     * @param int $userId
 +     * @param Request $request
 +     * @param string $listName
 +     * @return \Illuminate\Http\RedirectResponse
 +     */
 +    protected function changeListSort(int $userId, Request $request, string $listName)
 +    {
 +        $this->checkPermissionOrCurrentUser('users-manage', $userId);
 +
 +        $sort = $request->get('sort');
 +        if (!in_array($sort, ['name', 'created_at', 'updated_at'])) {
 +            $sort = 'name';
          }
  
 -        $user = $this->userRepo->getById($id);
 -        setting()->putUser($user, 'bookshelves_view_type', $viewType);
 +        $order = $request->get('order');
 +        if (!in_array($order, ['asc', 'desc'])) {
 +            $order = 'asc';
 +        }
  
 -        return redirect()->back(302, [], "/settings/users/$id");
 +        $user = $this->user->findOrFail($userId);
 +        $sortKey = $listName . '_sort';
 +        $orderKey = $listName . '_sort_order';
 +        setting()->putUser($user, $sortKey, $sort);
 +        setting()->putUser($user, $orderKey, $order);
 +
 +        return redirect()->back(302, [], "/settings/users/$userId");
      }
 +
  }
index 3e42c9feb4abdc1ec445813182dd4f0475e44769,ac2edc621f9256b80eb1c1729b2bf91aafd3bbba..1f71c327238ab3fb3f4bae4489c5a8bda48d4183
@@@ -1,9 -1,10 +1,10 @@@
  <?php
+ /**
+  * Common elements found throughout many areas of BookStack.
+  */
  return [
  
-     /**
-      * Buttons
-      */
+     // Buttons
      'cancel' => 'Cancel',
      'confirm' => 'Confirm',
      'back' => 'Back',
      'select' => 'Select',
      'more' => 'More',
  
-     /**
-      * Form Labels
-      */
+     // Form Labels
      'name' => 'Name',
      'description' => 'Description',
      'role' => 'Role',
      'cover_image' => 'Cover image',
      'cover_image_description' => 'This image should be approx 440x250px.',
      
-     /**
-      * Actions
-      */
+     // Actions
      'actions' => 'Actions',
      'view' => 'View',
      'create' => 'Create',
      'remove' => 'Remove',
      'add' => 'Add',
  
-     /**
-      * Sort Options
-      */
++    // Sort Options
 +    'sort_name' => 'Name',
 +    'sort_created_at' => 'Created Date',
 +    'sort_updated_at' => 'Updated Date',
 +
-     /**
-      * Misc
-      */
+     // Misc
      'deleted_user' => 'Deleted User',
      'no_activity' => 'No activity to show',
      'no_items' => 'No items available',
      'list_view' => 'List View',
      'default' => 'Default',
  
-     /**
-      * Header
-      */
+     // Header
      'view_profile' => 'View Profile',
      'edit_profile' => 'Edit Profile',
  
-     /**
-      * Email Content
-      */
+     // Email Content
      'email_action_help' => 'If you’re having trouble clicking the ":actionText" button, copy and paste the URL below into your web browser:',
      'email_rights' => 'All rights reserved',
--];
++];
index 7f309e21f81b24b67a861ef1215393b2225b1681,0000000000000000000000000000000000000000..67aa6759989c0c41e4adbd073204a3e7a263d7f0
mode 100644,000000..100644
--- /dev/null
@@@ -1,58 -1,0 +1,64 @@@
-                         <a href="{{ baseUrl('/login') }}">@icon('login'){{ trans('auth.log_in') }}</a>
 +<header id="header" header-mobile-toggle>
 +    <div class="grid break-l mx-l">
 +        <div>
 +            <a href="{{ baseUrl('/') }}" class="logo">
 +                @if(setting('app-logo', '') !== 'none')
 +                    <img class="logo-image" src="{{ setting('app-logo', '') === '' ? baseUrl('/logo.png') : baseUrl(setting('app-logo', '')) }}" alt="Logo">
 +                @endif
 +                @if (setting('app-name-header'))
 +                    <span class="logo-text">{{ setting('app-name') }}</span>
 +                @endif
 +            </a>
 +            <div class="mobile-menu-toggle hide-over-l">@icon('more')</div>
 +        </div>
 +        <div class="header-search hide-under-l">
 +            <form action="{{ baseUrl('/search') }}" method="GET" class="search-box">
 +                <button id="header-search-box-button" type="submit">@icon('search') </button>
 +                <input id="header-search-box-input" type="text" name="term" tabindex="2" placeholder="{{ trans('common.search') }}" value="{{ isset($searchTerm) ? $searchTerm : '' }}">
 +            </form>
 +        </div>
 +        <div class="text-right">
 +            <div class="header-links">
 +                <div class="links text-center">
 +                    <a class="hide-over-l" href="{{ baseUrl('/search') }}">@icon('search'){{ trans('common.search') }}</a>
 +                    @if(userCan('bookshelf-view-all') || userCan('bookshelf-view-own'))
 +                        <a href="{{ baseUrl('/shelves') }}">@icon('bookshelf'){{ trans('entities.shelves') }}</a>
 +                    @endif
 +                    <a href="{{ baseUrl('/books') }}">@icon('book'){{ trans('entities.books') }}</a>
 +                    @if(signedInUser() && userCan('settings-manage'))
 +                        <a href="{{ baseUrl('/settings') }}">@icon('settings'){{ trans('settings.settings') }}</a>
 +                    @endif
++                    @if(signedInUser() && userCan('users-manage') && !userCan('settings-manage'))
++                        <a href="{{ baseUrl('/settings/users') }}">@icon('users'){{ trans('settings.users') }}</a>
++                    @endif
 +                    @if(!signedInUser())
++                        @if(setting('registration-enabled', false))
++                            <a href="{{ baseUrl("/register") }}">@icon('new-user') {{ trans('auth.sign_up') }}</a>
++                        @endif
++                        <a href="{{ baseUrl('/login') }}">@icon('login') {{ trans('auth.log_in') }}</a>
 +                    @endif
 +                </div>
 +                @if(signedInUser())
 +                    <?php $currentUser = user(); ?>
 +                    <div class="dropdown-container" dropdown>
 +                        <span class="user-name hide-under-l" dropdown-toggle>
 +                            <img class="avatar" src="{{$currentUser->getAvatar(30)}}" alt="{{ $currentUser->name }}">
 +                            <span class="name">{{ $currentUser->getShortName(9) }}</span> @icon('caret-down')
 +                        </span>
 +                        <ul>
 +                            <li>
 +                                <a href="{{ baseUrl("/user/{$currentUser->id}") }}" class="text-primary">@icon('user'){{ trans('common.view_profile') }}</a>
 +                            </li>
 +                            <li>
 +                                <a href="{{ baseUrl("/settings/users/{$currentUser->id}") }}" class="text-primary">@icon('edit'){{ trans('common.edit_profile') }}</a>
 +                            </li>
 +                            <li>
 +                                <a href="{{ baseUrl('/logout') }}" class="text-neg">@icon('logout'){{ trans('auth.logout') }}</a>
 +                            </li>
 +                        </ul>
 +                    </div>
 +                @endif
 +            </div>
 +        </div>
 +    </div>
 +</header>
index 282ae21d262442a7073fea32e4f8abf2af48edd1,afe007d45fe057df7dc24f58065d40a1985b41a0..f7f8e2cf23f7d8f7b893b1af190c9138bf7a6207
@@@ -1,7 -1,48 +1,7 @@@
 -@extends('sidebar-layout')
 +@extends('tri-layout')
 +@section('container-classes', 'mt-xl')
  
 -@section('toolbar')
 -    <div class="col-sm-8 col-xs-5 faded">
 -        @include('pages._breadcrumbs', ['page' => $page])
 -    </div>
 -    <div class="col-sm-4 col-xs-7 faded">
 -        <div class="action-buttons">
 -            <span dropdown class="dropdown-container">
 -                <div dropdown-toggle class="text-button text-primary">@icon('export'){{ trans('entities.export') }}</div>
 -                <ul class="wide">
 -                    <li><a href="{{ $page->getUrl('/export/html') }}" target="_blank">{{ trans('entities.export_html') }} <span class="text-muted float right">.html</span></a></li>
 -                    <li><a href="{{ $page->getUrl('/export/pdf') }}" target="_blank">{{ trans('entities.export_pdf') }} <span class="text-muted float right">.pdf</span></a></li>
 -                    <li><a href="{{ $page->getUrl('/export/plaintext') }}" target="_blank">{{ trans('entities.export_text') }} <span class="text-muted float right">.txt</span></a></li>
 -                </ul>
 -            </span>
 -            @if(userCan('page-update', $page))
 -                <a href="{{ $page->getUrl('/edit') }}" class="text-primary text-button" >@icon('edit'){{ trans('common.edit') }}</a>
 -            @endif
 -            @if(userCan('page-update', $page) || userCan('restrictions-manage', $page) || userCan('page-delete', $page))
 -                <div dropdown class="dropdown-container">
 -                    <a dropdown-toggle class="text-primary text-button">@icon('more') {{ trans('common.more') }}</a>
 -                    <ul>
 -                        @if(userCan('page-update', $page))
 -                            <li><a href="{{ $page->getUrl('/copy') }}" class="text-primary" >@icon('copy'){{ trans('common.copy') }}</a></li>
 -                            @if(userCan('page-delete', $page))
 -                                <li><a href="{{ $page->getUrl('/move') }}" class="text-primary" >@icon('folder'){{ trans('common.move') }}</a></li>
 -                            @endif
 -                            <li><a href="{{ $page->getUrl('/revisions') }}" class="text-primary">@icon('history'){{ trans('entities.revisions') }}</a></li>
 -                        @endif
 -                        @if(userCan('restrictions-manage', $page))
 -                            <li><a href="{{ $page->getUrl('/permissions') }}" class="text-primary">@icon('lock'){{ trans('entities.permissions') }}</a></li>
 -                        @endif
 -                        @if(userCan('page-delete', $page))
 -                            <li><a href="{{ $page->getUrl('/delete') }}" class="text-neg">@icon('delete'){{ trans('common.delete') }}</a></li>
 -                        @endif
 -                    </ul>
 -                </div>
 -            @endif
 -
 -        </div>
 -    </div>
 -@stop
 -
 -@section('sidebar')
 +@section('left')
  
      @if($page->tags->count() > 0)
          <section>
@@@ -10,8 -51,8 +10,8 @@@
      @endif
  
      @if ($page->attachments->count() > 0)
 -        <div class="card">
 -            <h3>@icon('attach') {{ trans('entities.pages_attachments') }}</h3>
 +        <div id="page-attachments" class="mb-xl">
 +            <h5>{{ trans('entities.pages_attachments') }}</h5>
              <div class="body">
                  @foreach($page->attachments as $attachment)
                      <div class="attachment">
@@@ -23,8 -64,8 +23,8 @@@
      @endif
  
      @if (isset($pageNav) && count($pageNav))
 -        <div class="card">
 -            <h3>@icon('open-book') {{ trans('entities.pages_navigation') }}</h3>
 +        <div id="page-navigation" class="mb-xl">
 +            <h5>{{ trans('entities.pages_navigation') }}</h5>
              <div class="body">
                  <div class="sidebar-page-nav menu">
                      @foreach($pageNav as $navItem)
@@@ -37,8 -78,8 +37,8 @@@
          </div>
      @endif
  
 -    <div class="card entity-details">
 -        <h3>@icon('info') {{ trans('common.details') }}</h3>
 +    <div id="page-details" class="entity-details mb-xl">
 +        <h5>{{ trans('common.details') }}</h5>
          <div class="body text-muted text-small blended-links">
              @include('partials.entity-meta', ['entity' => $page])
  
          </div>
      </div>
  
 -    @include('partials/book-tree', ['book' => $book, 'sidebarTree' => $sidebarTree])
 -
 +    @include('partials.book-tree', ['book' => $book, 'sidebarTree' => $sidebarTree])
  @stop
  
 -@section('body-wrap-classes', 'flex-fill columns')
 -
  @section('body')
  
 -    <div class="page-content flex" page-display="{{ $page->id }}">
 +    <div class="mb-m">
 +        @include('pages._breadcrumbs', ['page' => $page])
 +    </div>
 +
 +    <div class="content-wrap card">
 +        <div class="page-content flex" page-display="{{ $page->id }}">
  
 -        <div class="pointer-container" id="pointer">
 -            <div class="pointer anim {{ userCan('page-update', $page) ? 'is-page-editable' : ''}}" >
 -                <span class="icon text-primary">@icon('link') @icon('include', ['style' => 'display:none;'])</span>
 -                <span class="input-group">
 +            <div class="pointer-container" id="pointer">
 +                <div class="pointer anim {{ userCan('page-update', $page) ? 'is-page-editable' : ''}}" >
 +                    <span class="icon text-primary">@icon('link') @icon('include', ['style' => 'display:none;'])</span>
 +                    <span class="input-group">
                      <input readonly="readonly" type="text" id="pointer-url" placeholder="url">
                      <button class="button icon" data-clipboard-target="#pointer-url" type="button" title="{{ trans('entities.pages_copy_link') }}">@icon('copy')</button>
                  </span>
 -                @if(userCan('page-update', $page))
 -                    <a href="{{ $page->getUrl('/edit') }}" id="pointer-edit" data-edit-href="{{ $page->getUrl('/edit') }}"
 -                        class="button icon heading-edit-icon" title="{{ trans('entities.pages_edit_content_link')}}">@icon('edit')</a>
 -                @endif
 +                    @if(userCan('page-update', $page))
 +                        <a href="{{ $page->getUrl('/edit') }}" id="pointer-edit" data-edit-href="{{ $page->getUrl('/edit') }}"
 +                           class="button icon heading-edit-icon" title="{{ trans('entities.pages_edit_content_link')}}">@icon('edit')</a>
 +                    @endif
 +                </div>
              </div>
 -        </div>
  
 -        @include('pages/page-display')
 +            @include('pages.page-display')
 +        </div>
      </div>
  
      @if ($commentsEnabled)
 -      <div class="container small nopad comments-container">
 -          @include('comments/comments', ['page' => $page])
 +      <div class="container small nopad comments-container mb-l">
 +          @include('comments.comments', ['page' => $page])
 +          <div class="clearfix"></div>
        </div>
      @endif
  @stop
-                 <a href="{{ $page->getUrl('/move') }}" class="icon-list-item">
-                     <span class="icon">@icon('folder')</span>
-                     <span>{{ trans('common.move') }}</span>
-                 </a>
 +
 +@section('right')
 +    <div class="actions mb-xl">
 +        <h5>Actions</h5>
 +
 +        <div class="icon-list text-primary">
 +            {{--Export--}}
 +            <div dropdown class="dropdown-container block">
 +                <div dropdown-toggle class="icon-list-item">
 +                    <span class="icon">@icon('export')</span>
 +                    <span>{{ trans('entities.export') }}</span>
 +                </div>
 +                <ul class="wide">
 +                    <li><a href="{{ $page->getUrl('/export/html') }}" target="_blank">{{ trans('entities.export_html') }} <span class="text-muted float right">.html</span></a></li>
 +                    <li><a href="{{ $page->getUrl('/export/pdf') }}" target="_blank">{{ trans('entities.export_pdf') }} <span class="text-muted float right">.pdf</span></a></li>
 +                    <li><a href="{{ $page->getUrl('/export/plaintext') }}" target="_blank">{{ trans('entities.export_text') }} <span class="text-muted float right">.txt</span></a></li>
 +                </ul>
 +            </div>
 +
 +            {{--User Actions--}}
 +            @if(userCan('page-update', $page))
 +                <a href="{{ $page->getUrl('/edit') }}" class="icon-list-item">
 +                    <span class="icon">@icon('edit')</span>
 +                    <span>{{ trans('common.edit') }}</span>
 +                </a>
 +                <a href="{{ $page->getUrl('/copy') }}" class="icon-list-item">
 +                    <span class="icon">@icon('copy')</span>
 +                    <span>{{ trans('common.copy') }}</span>
 +                </a>
++                @if(userCan('page-delete', $page))
++                      <a href="{{ $page->getUrl('/move') }}" class="icon-list-item">
++                          <span class="icon">@icon('folder')</span>
++                          <span>{{ trans('common.move') }}</span>
++                      </a>
++                @endif
 +                <a href="{{ $page->getUrl('/revisions') }}" class="icon-list-item">
 +                    <span class="icon">@icon('history')</span>
 +                    <span>{{ trans('entities.revisions') }}</span>
 +                </a>
 +            @endif
 +            @if(userCan('restrictions-manage', $page))
 +                <a href="{{ $page->getUrl('/permissions') }}" class="icon-list-item">
 +                    <span class="icon">@icon('lock')</span>
 +                    <span>{{ trans('entities.permissions') }}</span>
 +                </a>
 +            @endif
 +            @if(userCan('page-delete', $page))
 +                <a href="{{ $page->getUrl('/delete') }}" class="icon-list-item">
 +                    <span class="icon">@icon('delete')</span>
 +                    <span>{{ trans('common.delete') }}</span>
 +                </a>
 +            @endif
 +        </div>
 +
 +    </div>
 +@stop