Commit 7fb1de2c authored by Yassine Doghri's avatar Yassine Doghri
Browse files

feat: add breadcrumb in admin area

- add Breadcrumb library and service
- update authorizations
- add missing routes to avoid 404 links in breadcrumb
- add svg_helper globally in base controller
- update purgecss config to check .ts files

closes #17
parent ed6e9530
......@@ -49,7 +49,7 @@ class FlatAuthorization extends \Myth\Auth\Authorization\FlatAuthorization
}
/**
* Makes a member a part of multiple groups.
* Makes user part of given groups.
*
* @param $userId
* @param array|null $groups // Either collection of ID or names
......
......@@ -77,11 +77,11 @@ $routes->group(
$routes->get('podcasts', 'Podcast::list', [
'as' => 'podcast_list',
]);
$routes->get('new-podcast', 'Podcast::create', [
$routes->get('podcasts/new', 'Podcast::create', [
'as' => 'podcast_create',
'filter' => 'permission:podcasts-create',
]);
$routes->post('new-podcast', 'Podcast::attemptCreate', [
$routes->post('podcasts/new', 'Podcast::attemptCreate', [
'filter' => 'permission:podcasts-create',
]);
......@@ -108,19 +108,19 @@ $routes->group(
'as' => 'episode_list',
'filter' => 'permission:podcasts-view,podcast-view',
]);
$routes->get('new-episode', 'Episode::create/$1', [
$routes->get('episodes/new', 'Episode::create/$1', [
'as' => 'episode_create',
'filter' =>
'permission:episodes-create,podcast_episodes-create',
]);
$routes->post('new-episode', 'Episode::attemptCreate/$1', [
$routes->post('episodes/new', 'Episode::attemptCreate/$1', [
'filter' =>
'permission:episodes-create,podcast_episodes-create',
]);
$routes->get('episodes/(:num)', 'Episode::view/$1/$2', [
'as' => 'episode_view',
'filter' => 'permission:episodes-list,podcast_episodes-list',
'filter' => 'permission:episodes-view,podcast_episodes-view',
]);
$routes->get('episodes/(:num)/edit', 'Episode::edit/$1/$2', [
'as' => 'episode_edit',
......@@ -146,15 +146,18 @@ $routes->group(
'filter' =>
'permission:podcasts-manage_contributors,podcast-manage_contributors',
]);
$routes->get('add-contributor', 'Contributor::add/$1', [
$routes->get('contributors/add', 'Contributor::add/$1', [
'as' => 'contributor_add',
'filter' =>
'permission:podcasts-manage_contributors,podcast-manage_contributors',
]);
$routes->post('add-contributor', 'Contributor::attemptAdd/$1', [
$routes->post('contributors/add', 'Contributor::attemptAdd/$1', [
'filter' =>
'permission:podcasts-manage_contributors,podcast-manage_contributors',
]);
$routes->get('contributors/(:num)', 'Contributor::view/$1/$2', [
'as' => 'contributor_view',
]);
$routes->get(
'contributors/(:num)/edit',
'Contributor::edit/$1/$2',
......@@ -188,11 +191,15 @@ $routes->group(
'as' => 'user_list',
'filter' => 'permission:users-list',
]);
$routes->get('new-user', 'User::create', [
$routes->get('users/new', 'User::create', [
'as' => 'user_create',
'filter' => 'permission:users-create',
]);
$routes->post('new-user', 'User::attemptCreate', [
$routes->get('users/(:num)', 'User::view/$1', [
'as' => 'user_view',
'filter' => 'permission:users-view',
]);
$routes->post('users/new', 'User::attemptCreate', [
'filter' => 'permission:users-create',
]);
$routes->get('users/(:num)/edit', 'User::edit/$1', [
......
......@@ -7,6 +7,7 @@ use CodeIgniter\Model;
use App\Authorization\FlatAuthorization;
use App\Authorization\PermissionModel;
use App\Authorization\GroupModel;
use App\Libraries\Breadcrumb;
use App\Models\UserModel;
use Myth\Auth\Models\LoginModel;
......@@ -91,4 +92,13 @@ class Services extends CoreServices
return $instance->setUserModel($userModel);
}
public static function breadcrumb(bool $getShared = true)
{
if ($getShared) {
return self::getSharedInstance('breadcrumb');
}
return new Breadcrumb();
}
}
......@@ -26,7 +26,7 @@ class BaseController extends Controller
*
* @var array
*/
protected $helpers = ['auth'];
protected $helpers = ['auth', 'breadcrumb', 'svg'];
/**
* Constructor.
......
......@@ -41,7 +41,24 @@ class Contributor extends BaseController
'podcast' => $this->podcast,
];
echo view('admin/contributor/list', $data);
replace_breadcrumb_params([0 => $this->podcast->title]);
return view('admin/contributor/list', $data);
}
public function view()
{
$data = [
'contributor' => (new UserModel())->getPodcastContributor(
$this->user->id,
$this->podcast->id
),
];
replace_breadcrumb_params([
0 => $this->podcast->title,
1 => $this->user->username,
]);
return view('admin/contributor/view', $data);
}
public function add()
......@@ -52,7 +69,8 @@ class Contributor extends BaseController
'roles' => (new GroupModel())->getContributorRoles(),
];
echo view('admin/contributor/add', $data);
replace_breadcrumb_params([0 => $this->podcast->title]);
return view('admin/contributor/add', $data);
}
public function attemptAdd()
......@@ -87,7 +105,11 @@ class Contributor extends BaseController
'roles' => (new GroupModel())->getContributorRoles(),
];
echo view('admin/contributor/edit', $data);
replace_breadcrumb_params([
0 => $this->podcast->title,
1 => $this->user->username,
]);
return view('admin/contributor/edit', $data);
}
public function attemptEdit()
......
......@@ -42,6 +42,9 @@ class Episode extends BaseController
'podcast' => $this->podcast,
];
replace_breadcrumb_params([
0 => $this->podcast->title,
]);
return view('admin/episode/list', $data);
}
......@@ -49,6 +52,10 @@ class Episode extends BaseController
{
$data = ['episode' => $this->episode];
replace_breadcrumb_params([
0 => $this->podcast->title,
1 => $this->episode->title,
]);
return view('admin/episode/view', $data);
}
......@@ -60,7 +67,10 @@ class Episode extends BaseController
'podcast' => $this->podcast,
];
echo view('admin/episode/create', $data);
replace_breadcrumb_params([
0 => $this->podcast->title,
]);
return view('admin/episode/create', $data);
}
public function attemptCreate()
......@@ -115,7 +125,11 @@ class Episode extends BaseController
'episode' => $this->episode,
];
echo view('admin/episode/edit', $data);
replace_breadcrumb_params([
0 => $this->podcast->title,
1 => $this->episode->title,
]);
return view('admin/episode/edit', $data);
}
public function attemptEdit()
......
......@@ -52,6 +52,7 @@ class Podcast extends BaseController
{
$data = ['podcast' => $this->podcast];
replace_breadcrumb_params([0 => $this->podcast->title]);
return view('admin/podcast/view', $data);
}
......@@ -69,7 +70,7 @@ class Podcast extends BaseController
),
];
echo view('admin/podcast/create', $data);
return view('admin/podcast/create', $data);
}
public function attemptCreate()
......@@ -145,7 +146,8 @@ class Podcast extends BaseController
'categories' => (new CategoryModel())->findAll(),
];
echo view('admin/podcast/edit', $data);
replace_breadcrumb_params([0 => $this->podcast->title]);
return view('admin/podcast/edit', $data);
}
public function attemptEdit()
......
......@@ -34,13 +34,21 @@ class User extends BaseController
return view('admin/user/list', $data);
}
public function view()
{
$data = ['user' => $this->user];
replace_breadcrumb_params([0 => $this->user->username]);
return view('admin/user/view', $data);
}
public function create()
{
$data = [
'roles' => (new GroupModel())->getUserRoles(),
];
echo view('admin/user/create', $data);
return view('admin/user/create', $data);
}
public function attemptCreate()
......@@ -99,7 +107,8 @@ class User extends BaseController
'roles' => (new GroupModel())->getUserRoles(),
];
echo view('admin/user/edit', $data);
replace_breadcrumb_params([0 => $this->user->username]);
return view('admin/user/edit', $data);
}
public function attemptEdit()
......
......@@ -50,6 +50,11 @@ class AuthSeeder extends Seeder
'description' => 'List all users',
'has_permission' => ['superadmin'],
],
[
'name' => 'view',
'description' => 'View any user info',
'has_permission' => ['superadmin'],
],
[
'name' => 'manage_authorizations',
'description' => 'Add or remove roles/permissions to a user',
......@@ -128,6 +133,11 @@ class AuthSeeder extends Seeder
'description' => 'List all episodes of any podcast',
'has_permission' => ['superadmin'],
],
[
'name' => 'view',
'description' => 'View any episode of any podcast',
'has_permission' => ['superadmin'],
],
[
'name' => 'create',
'description' => 'Add a new episode to any podcast',
......@@ -195,6 +205,11 @@ class AuthSeeder extends Seeder
'description' => 'List all episodes of a podcast',
'has_permission' => ['podcast_admin'],
],
[
'name' => 'view',
'description' => 'View any episode of a podcast',
'has_permission' => ['podcast_admin'],
],
[
'name' => 'create',
'description' => 'Add new episodes for a podcast',
......
......@@ -12,6 +12,12 @@ class User extends \Myth\Auth\Entities\User
*/
protected $podcasts = [];
/**
* The podcast user is contributing to
* @var \App\Entities\Podcast
*/
protected $podcast;
/**
* Array of field names and the type of value to cast them as
* when they are accessed.
......@@ -20,6 +26,7 @@ class User extends \Myth\Auth\Entities\User
'active' => 'boolean',
'force_pass_reset' => 'boolean',
'podcast_role' => '?string',
'podcast_id' => '?integer',
];
/**
......@@ -41,4 +48,19 @@ class User extends \Myth\Auth\Entities\User
return $this->podcasts;
}
public function getPodcast()
{
if (empty($this->podcast_id)) {
throw new \RuntimeException(
'Podcast_id must be set before getting podcast.'
);
}
if (empty($this->podcast)) {
$this->podcast = (new PodcastModel())->find($this->podcast_id);
}
return $this->podcast;
}
}
<?php
/**
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
use Config\Services;
/**
* Returns the inline svg icon
*
* @param string $name name of the icon file without the .svg extension
* @param string $class to be added to the svg string
* @return string html breadcrumb
*/
function render_breadcrumb()
{
$breadcrumb = Services::breadcrumb();
return $breadcrumb->render();
}
function replace_breadcrumb_params($newParams)
{
$breadcrumb = Services::breadcrumb();
$breadcrumb->replaceParams($newParams);
}
<?php
/**
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
return [
'label' => 'breadcrumb',
config('App')->adminGateway => 'Home',
'my-podcasts' => 'my podcasts',
'podcasts' => 'podcasts',
'episodes' => 'episodes',
'contributors' => 'contributors',
'add' => 'add',
'new' => 'new',
'edit' => 'edit',
'users' => 'users',
'my-account' => 'my account',
'change-password' => 'change password',
];
......@@ -8,6 +8,7 @@
return [
'podcast_contributors' => 'Podcast contributors',
'view' => '{username}\'s contribution to {podcastName}',
'add' => 'Add contributor',
'add_contributor' => 'Add a contributor for {0}',
'edit_role' => 'Update role for {0}',
......
......@@ -8,8 +8,8 @@
return [
'info' => 'My account info',
'changePassword' => 'Change my password',
'messages' => [
'passwordChangeSuccess' => 'Password has been successfully changed!',
'changePassword' => 'Change my password',
],
];
......@@ -13,6 +13,7 @@ return [
'unban' => 'Unban',
'delete' => 'Delete',
'create' => 'Create a user',
'view' => '{username}\'s info',
'all_users' => 'All users',
'form' => [
'email' => 'Email',
......
<?php
/**
* Generates and renders a breadcrumb based on the current url segments
*
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Libraries;
class Breadcrumb
{
/**
* List of breadcrumb links.
*
* @var array
* $links = [
* 'text' => (string) the anchor text,
* 'href' => (string) the anchor href,
* ]
*/
protected $links = [];
/**
* Initializes the Breadcrumb object using the segments from
* current_url by populating the $links property with text and href data
*/
public function __construct()
{
$uri = '';
foreach (current_url(true)->getSegments() as $segment) {
$uri .= '/' . $segment;
array_push($this->links, [
'text' => is_numeric($segment)
? $segment
: lang('Breadcrumb.' . $segment),
'href' => base_url($uri),
]);
}
}
/**
* Replaces all numeric text in breadcrumb's $link property
* with new params at same position
*
* Given a breadcrumb with numeric params, this function
* replaces them with the values provided in $newParams
*
* Example with `Home / podcasts / 1 / episodes / 1`
*
* $newParams = [
* 0 => 'foo',
* 1 => 'bar'
* ]
* replaceParams($newParams);
*
* The breadcrumb is now `Home / podcasts / foo / episodes / bar`
*
* @param array $newParams
*/
public function replaceParams($newParams)
{
foreach ($this->links as $key => $link) {
if (is_numeric($link['text'])) {
$this->links[$key]['text'] = $newParams[0];
array_shift($newParams);
}
}
}
/**
* Renders the breadcrumb object as an accessible html breadcrumb nav
*
* @return string
*/
public function render()
{
$listItems = '';
$keys = array_keys($this->links);
foreach ($this->links as $key => $link) {
if (end($keys) == $key) {
$listItem =
'<li class="breadcrumb-item active" aria-current="page">' .
$link['text'] .
'</li>';
} else {
$listItem =
'<li class="breadcrumb-item">' .
anchor($link['href'], $link['text']) .
'</li>';
}
$listItems .= $listItem;
}
return '<nav aria-label="' .
lang('Breadcrumb.label') .
'"><ol class="breadcrumb">' .
$listItems .
'</ol></nav>';
}
}
......@@ -17,8 +17,11 @@ class UserModel extends \Myth\Auth\Models\UserModel
public function getPodcastContributor($user_id, $podcast_id)
{
return $this->select('users.*')
return $this->select(
'users.*, users_podcasts.podcast_id as podcast_id, auth_groups.name as podcast_role'
)
->join('users_podcasts', 'users_podcasts.user_id = users.id')
->join('auth_groups', 'auth_groups.id = users_podcasts.group_id')
->where([
'users.id' => $user_id,
'podcast_id' => $podcast_id,
......
.breadcrumb {
@apply inline-flex flex-wrap px-1 py-2 text-sm text-gray-800;
}
.breadcrumb-item + .breadcrumb-item::before {
@apply inline-block px-1 text-gray-500;
content: "/";
}
.breadcrumb-item a {
@apply no-underline;
&:hover {
@apply underline;
}
}
.breadcrumb-item.active {
@apply font-semibold;
}
@import "./tailwind.css";
@import "./layout.css";
@import "./breadcrumb.css";