diff --git a/app/Authorization/FlatAuthorization.php b/app/Authorization/FlatAuthorization.php index a732a8cb..f96fb2aa 100644 --- a/app/Authorization/FlatAuthorization.php +++ b/app/Authorization/FlatAuthorization.php @@ -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 diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 84a96218..e288b15e 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -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', [ diff --git a/app/Config/Services.php b/app/Config/Services.php index 275caab2..27d601fa 100644 --- a/app/Config/Services.php +++ b/app/Config/Services.php @@ -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(); + } } diff --git a/app/Controllers/Admin/BaseController.php b/app/Controllers/Admin/BaseController.php index a10692c9..92f4849a 100644 --- a/app/Controllers/Admin/BaseController.php +++ b/app/Controllers/Admin/BaseController.php @@ -26,7 +26,7 @@ class BaseController extends Controller * * @var array */ - protected $helpers = ['auth']; + protected $helpers = ['auth', 'breadcrumb', 'svg']; /** * Constructor. diff --git a/app/Controllers/Admin/Contributor.php b/app/Controllers/Admin/Contributor.php index 4f5a8ae8..2f8ed6d5 100644 --- a/app/Controllers/Admin/Contributor.php +++ b/app/Controllers/Admin/Contributor.php @@ -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() diff --git a/app/Controllers/Admin/Episode.php b/app/Controllers/Admin/Episode.php index 0c5ce4c2..b639512e 100644 --- a/app/Controllers/Admin/Episode.php +++ b/app/Controllers/Admin/Episode.php @@ -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() diff --git a/app/Controllers/Admin/Podcast.php b/app/Controllers/Admin/Podcast.php index 99209e07..c0e5b2cb 100644 --- a/app/Controllers/Admin/Podcast.php +++ b/app/Controllers/Admin/Podcast.php @@ -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() diff --git a/app/Controllers/Admin/User.php b/app/Controllers/Admin/User.php index 98470cf1..872d973f 100644 --- a/app/Controllers/Admin/User.php +++ b/app/Controllers/Admin/User.php @@ -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() diff --git a/app/Database/Seeds/AuthSeeder.php b/app/Database/Seeds/AuthSeeder.php index 5ce7edcc..6ddfd950 100644 --- a/app/Database/Seeds/AuthSeeder.php +++ b/app/Database/Seeds/AuthSeeder.php @@ -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', diff --git a/app/Entities/User.php b/app/Entities/User.php index f710fd06..88fe6ed9 100644 --- a/app/Entities/User.php +++ b/app/Entities/User.php @@ -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; + } } diff --git a/app/Helpers/breadcrumb_helper.php b/app/Helpers/breadcrumb_helper.php new file mode 100644 index 00000000..52022860 --- /dev/null +++ b/app/Helpers/breadcrumb_helper.php @@ -0,0 +1,28 @@ +render(); +} + +function replace_breadcrumb_params($newParams) +{ + $breadcrumb = Services::breadcrumb(); + $breadcrumb->replaceParams($newParams); +} diff --git a/app/Helpers/html_helper.php b/app/Helpers/svg_helper.php similarity index 100% rename from app/Helpers/html_helper.php rename to app/Helpers/svg_helper.php diff --git a/app/Language/en/Breadcrumb.php b/app/Language/en/Breadcrumb.php new file mode 100644 index 00000000..6ef22d33 --- /dev/null +++ b/app/Language/en/Breadcrumb.php @@ -0,0 +1,22 @@ + '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', +]; diff --git a/app/Language/en/Contributor.php b/app/Language/en/Contributor.php index a9bf108b..25fed243 100644 --- a/app/Language/en/Contributor.php +++ b/app/Language/en/Contributor.php @@ -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}', diff --git a/app/Language/en/MyAccount.php b/app/Language/en/MyAccount.php index c65d9344..b675cc17 100644 --- a/app/Language/en/MyAccount.php +++ b/app/Language/en/MyAccount.php @@ -8,8 +8,8 @@ return [ 'info' => 'My account info', + 'changePassword' => 'Change my password', 'messages' => [ 'passwordChangeSuccess' => 'Password has been successfully changed!', - 'changePassword' => 'Change my password', ], ]; diff --git a/app/Language/en/User.php b/app/Language/en/User.php index 2cdc1e33..0e742c2a 100644 --- a/app/Language/en/User.php +++ b/app/Language/en/User.php @@ -13,6 +13,7 @@ return [ 'unban' => 'Unban', 'delete' => 'Delete', 'create' => 'Create a user', + 'view' => '{username}\'s info', 'all_users' => 'All users', 'form' => [ 'email' => 'Email', diff --git a/app/Libraries/Breadcrumb.php b/app/Libraries/Breadcrumb.php new file mode 100644 index 00000000..43cb2f95 --- /dev/null +++ b/app/Libraries/Breadcrumb.php @@ -0,0 +1,104 @@ + (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 = + ''; + } else { + $listItem = + ''; + } + + $listItems .= $listItem; + } + + return ''; + } +} diff --git a/app/Models/UserModel.php b/app/Models/UserModel.php index 420c002d..4f9f7d03 100644 --- a/app/Models/UserModel.php +++ b/app/Models/UserModel.php @@ -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, diff --git a/app/Views/_assets/styles/breadcrumb.css b/app/Views/_assets/styles/breadcrumb.css new file mode 100644 index 00000000..f2cb9162 --- /dev/null +++ b/app/Views/_assets/styles/breadcrumb.css @@ -0,0 +1,20 @@ +.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; +} diff --git a/app/Views/_assets/styles/index.css b/app/Views/_assets/styles/index.css index 5f4c3b7f..f12f46bb 100644 --- a/app/Views/_assets/styles/index.css +++ b/app/Views/_assets/styles/index.css @@ -1,2 +1,3 @@ @import "./tailwind.css"; @import "./layout.css"; +@import "./breadcrumb.css"; diff --git a/app/Views/admin/_header.php b/app/Views/admin/_header.php index 3fc1d877..b97236e3 100644 --- a/app/Views/admin/_header.php +++ b/app/Views/admin/_header.php @@ -1,10 +1,13 @@
- - - Admin - +
+ + + Admin + +
+
diff --git a/app/Views/admin/contributor/list.php b/app/Views/admin/contributor/list.php index f80732a2..b0e7eb41 100644 --- a/app/Views/admin/contributor/list.php +++ b/app/Views/admin/contributor/list.php @@ -1,5 +1,3 @@ - - extend('admin/_layout') ?> section('title') ?> diff --git a/app/Views/admin/contributor/view.php b/app/Views/admin/contributor/view.php new file mode 100644 index 00000000..2aca7825 --- /dev/null +++ b/app/Views/admin/contributor/view.php @@ -0,0 +1,28 @@ +extend('admin/_layout') ?> + +section('title') ?> + $contributor->username, + 'podcastName' => $contributor->podcast->name, +]) ?> +endSection() ?> + + +section('content') ?> +
+
+ Username +
+
+ username ?> +
+
+
+
+ Role +
+
+ podcast_role ?> +
+
+endSection() ?> diff --git a/app/Views/admin/episode/list.php b/app/Views/admin/episode/list.php index 1b86523d..59f457c4 100644 --- a/app/Views/admin/episode/list.php +++ b/app/Views/admin/episode/list.php @@ -3,17 +3,18 @@ section('title') ?> (episodes) ?>) + + + endSection() ?> section('content') ?> - - $podcast->episodes, ]) ?> diff --git a/app/Views/admin/episode/view.php b/app/Views/admin/episode/view.php index 5bea39c4..90b9a343 100644 --- a/app/Views/admin/episode/view.php +++ b/app/Views/admin/episode/view.php @@ -1,12 +1,11 @@ extend('admin/_layout') ?> +section('title') ?> +title ?> +endSection() ?> + section('content') ?> -< -

title ?>

Episode cover