feat: add user permissions and basic groups to handle authorizations
- add AuthSeeder to bootstrap authorization data and remove UserSeeder - create a superadmin group having all authorizations - refactor routes and controller methods to separate get and post requests - refactor admin views with a title section in layout - add contributors section to podcasts to manage contributions (add, edit roles and remove) closes #3, #18
This commit is contained in:
parent
c63a077618
commit
d58e51874a
|
@ -36,11 +36,12 @@ $routes->addPlaceholder('username', '[a-zA-Z0-9 ]{3,}');
|
|||
// route since we don't have to scan directories.
|
||||
$routes->get('/', 'Home::index', ['as' => 'home']);
|
||||
|
||||
// Public routes
|
||||
$routes->group('@(:podcastName)', function ($routes) {
|
||||
$routes->add('/', 'Podcast/$1', ['as' => 'podcast']);
|
||||
$routes->get('/', 'Podcast/$1', ['as' => 'podcast']);
|
||||
|
||||
$routes->add('feed.xml', 'Feed/$1', ['as' => 'podcast_feed']);
|
||||
$routes->add('episodes/(:episodeSlug)', 'Episode/$1/$2', [
|
||||
$routes->get('feed.xml', 'Feed/$1', ['as' => 'podcast_feed']);
|
||||
$routes->get('episodes/(:episodeSlug)', 'Episode/$1/$2', [
|
||||
'as' => 'episode',
|
||||
]);
|
||||
});
|
||||
|
@ -51,74 +52,119 @@ $routes->add('stats/(:num)/(:num)/(:any)', 'Analytics::hit/$1/$2/$3', [
|
|||
]);
|
||||
|
||||
// Show the Unknown UserAgents
|
||||
$routes->add('.well-known/unknown-useragents', 'UnknownUserAgents');
|
||||
$routes->add('.well-known/unknown-useragents/(:num)', 'UnknownUserAgents/$1');
|
||||
$routes->get('.well-known/unknown-useragents', 'UnknownUserAgents');
|
||||
$routes->get('.well-known/unknown-useragents/(:num)', 'UnknownUserAgents/$1');
|
||||
|
||||
// Admin area
|
||||
$routes->group(
|
||||
config('App')->adminGateway,
|
||||
['namespace' => 'App\Controllers\Admin'],
|
||||
function ($routes) {
|
||||
$routes->add('/', 'Home', [
|
||||
$routes->get('/', 'Home', [
|
||||
'as' => 'admin',
|
||||
]);
|
||||
|
||||
$routes->add('new-podcast', 'Podcast::create', [
|
||||
'as' => 'podcast_create',
|
||||
$routes->get('my-podcasts', 'Podcast::myPodcasts', [
|
||||
'as' => 'my_podcasts',
|
||||
]);
|
||||
$routes->get('podcasts', 'Podcast::list', [
|
||||
'as' => 'podcast_list',
|
||||
'filter' => 'permission:podcasts-list',
|
||||
]);
|
||||
$routes->get('new-podcast', 'Podcast::create', [
|
||||
'as' => 'podcast_create',
|
||||
'filter' => 'permission:podcasts-create',
|
||||
]);
|
||||
$routes->post('new-podcast', 'Podcast::attemptCreate', [
|
||||
'filter' => 'permission:podcasts-create',
|
||||
]);
|
||||
$routes->add('podcasts', 'Podcast::list', ['as' => 'podcast_list']);
|
||||
|
||||
$routes->group('podcasts/@(:podcastName)', function ($routes) {
|
||||
$routes->add('edit', 'Podcast::edit/$1', [
|
||||
// Use ids in admin area to help permission and group lookups
|
||||
$routes->group('podcasts/(:num)', function ($routes) {
|
||||
$routes->get('edit', 'Podcast::edit/$1', [
|
||||
'as' => 'podcast_edit',
|
||||
]);
|
||||
$routes->post('edit', 'Podcast::attemptEdit/$1');
|
||||
$routes->add('delete', 'Podcast::delete/$1', [
|
||||
'as' => 'podcast_delete',
|
||||
]);
|
||||
|
||||
$routes->add('new-episode', 'Episode::create/$1', [
|
||||
'as' => 'episode_create',
|
||||
]);
|
||||
$routes->add('episodes', 'Episode::list/$1', [
|
||||
// Podcast episodes
|
||||
$routes->get('episodes', 'Episode::list/$1', [
|
||||
'as' => 'episode_list',
|
||||
]);
|
||||
$routes->get('new-episode', 'Episode::create/$1', [
|
||||
'as' => 'episode_create',
|
||||
]);
|
||||
$routes->post('new-episode', 'Episode::attemptCreate/$1');
|
||||
|
||||
$routes->add(
|
||||
'episodes/(:episodeSlug)/edit',
|
||||
'Episode::edit/$1/$2',
|
||||
$routes->get('episodes/(:num)/edit', 'Episode::edit/$1/$2', [
|
||||
'as' => 'episode_edit',
|
||||
]);
|
||||
$routes->post('episodes/(:num)/edit', 'Episode::attemptEdit/$1/$2');
|
||||
$routes->add('episodes/(:num)/delete', 'Episode::delete/$1/$2', [
|
||||
'as' => 'episode_delete',
|
||||
]);
|
||||
|
||||
// Podcast contributors
|
||||
$routes->get('contributors', 'Contributor::list/$1', [
|
||||
'as' => 'contributor_list',
|
||||
]);
|
||||
$routes->get('add-contributor', 'Contributor::add/$1', [
|
||||
'as' => 'contributor_add',
|
||||
]);
|
||||
$routes->post('add-contributor', 'Contributor::attemptAdd/$1');
|
||||
$routes->get(
|
||||
'contributors/(:num)/edit',
|
||||
'Contributor::edit/$1/$2',
|
||||
[
|
||||
'as' => 'episode_edit',
|
||||
'as' => 'contributor_edit',
|
||||
]
|
||||
);
|
||||
$routes->post(
|
||||
'contributors/(:num)/edit',
|
||||
'Contributor::attemptEdit/$1/$2'
|
||||
);
|
||||
$routes->add(
|
||||
'episodes/(:episodeSlug)/delete',
|
||||
'Episode::delete/$1/$2',
|
||||
[
|
||||
'as' => 'episode_delete',
|
||||
]
|
||||
'contributors/(:num)/remove',
|
||||
'Contributor::remove/$1/$2',
|
||||
['as' => 'contributor_remove']
|
||||
);
|
||||
});
|
||||
|
||||
// Users
|
||||
$routes->add('users', 'User::list', ['as' => 'user_list']);
|
||||
$routes->add('new-user', 'User::create', ['as' => 'user_create']);
|
||||
|
||||
$routes->add('users/@(:any)/ban', 'User::ban/$1', [
|
||||
'as' => 'user_ban',
|
||||
$routes->get('users', 'User::list', [
|
||||
'as' => 'user_list',
|
||||
'filter' => 'permission:users-list',
|
||||
]);
|
||||
$routes->add('users/@(:any)/unban', 'User::unBan/$1', [
|
||||
$routes->get('new-user', 'User::create', [
|
||||
'as' => 'user_create',
|
||||
'filter' => 'permission:users-create',
|
||||
]);
|
||||
$routes->post('new-user', 'User::attemptCreate', [
|
||||
'filter' => 'permission:users-create',
|
||||
]);
|
||||
|
||||
$routes->add('users/(:num)/ban', 'User::ban/$1', [
|
||||
'as' => 'user_ban',
|
||||
'filter' => 'permission:users-manage_bans',
|
||||
]);
|
||||
$routes->add('users/(:num)/unban', 'User::unBan/$1', [
|
||||
'as' => 'user_unban',
|
||||
'filter' => 'permission:users-manage_bans',
|
||||
]);
|
||||
$routes->add(
|
||||
'users/@(:any)/force-pass-reset',
|
||||
'users/(:num)/force-pass-reset',
|
||||
'User::forcePassReset/$1',
|
||||
[
|
||||
'as' => 'user_force_pass_reset',
|
||||
'filter' => 'permission:users-force_pass_reset',
|
||||
]
|
||||
);
|
||||
|
||||
$routes->add('users/@(:any)/delete', 'User::delete/$1', [
|
||||
$routes->add('users/(:num)/delete', 'User::delete/$1', [
|
||||
'as' => 'user_delete',
|
||||
'filter' => 'permission:users-delete',
|
||||
]);
|
||||
|
||||
// My account
|
||||
|
|
|
@ -0,0 +1,187 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright 2020 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Models\PodcastModel;
|
||||
use Myth\Auth\Authorization\GroupModel;
|
||||
use Myth\Auth\Config\Services;
|
||||
use Myth\Auth\Models\UserModel;
|
||||
|
||||
class Contributor extends BaseController
|
||||
{
|
||||
protected \App\Entities\Podcast $podcast;
|
||||
protected ?\Myth\Auth\Entities\User $user;
|
||||
|
||||
public function _remap($method, ...$params)
|
||||
{
|
||||
if (
|
||||
!has_permission('podcasts-manage_contributors') ||
|
||||
!has_permission("podcasts:$params[0]-manage_contributors")
|
||||
) {
|
||||
throw new \RuntimeException(lang('Auth.notEnoughPrivilege'));
|
||||
}
|
||||
|
||||
$podcast_model = new PodcastModel();
|
||||
|
||||
$this->podcast = $podcast_model->find($params[0]);
|
||||
|
||||
if (count($params) > 1) {
|
||||
$user_model = new UserModel();
|
||||
if (
|
||||
!($this->user = $user_model
|
||||
->select('users.*')
|
||||
->join(
|
||||
'users_podcasts',
|
||||
'users_podcasts.user_id = users.id'
|
||||
)
|
||||
->where([
|
||||
'users.id' => $params[1],
|
||||
'podcast_id' => $params[0],
|
||||
])
|
||||
->first())
|
||||
) {
|
||||
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
}
|
||||
|
||||
return $this->$method();
|
||||
}
|
||||
|
||||
public function list()
|
||||
{
|
||||
$data = [
|
||||
'podcast' => $this->podcast,
|
||||
];
|
||||
|
||||
echo view('admin/contributor/list', $data);
|
||||
}
|
||||
|
||||
public function add()
|
||||
{
|
||||
$user_model = new UserModel();
|
||||
$group_model = new GroupModel();
|
||||
|
||||
$roles = $group_model
|
||||
->select('auth_groups.*')
|
||||
->like('name', 'podcasts:' . $this->podcast->id, 'after')
|
||||
->findAll();
|
||||
|
||||
$data = [
|
||||
'podcast' => $this->podcast,
|
||||
'users' => $user_model->findAll(),
|
||||
'roles' => $roles,
|
||||
];
|
||||
|
||||
echo view('admin/contributor/add', $data);
|
||||
}
|
||||
|
||||
public function attemptAdd()
|
||||
{
|
||||
$authorize = Services::authorization();
|
||||
|
||||
$user_id = (int) $this->request->getPost('user');
|
||||
$group_id = (int) $this->request->getPost('role');
|
||||
|
||||
// Add user to chosen group
|
||||
$authorize->addUserToGroup($user_id, $group_id);
|
||||
|
||||
(new PodcastModel())->addContributorToPodcast(
|
||||
$user_id,
|
||||
$this->podcast->id
|
||||
);
|
||||
|
||||
return redirect()->route('contributor_list', [$this->podcast->id]);
|
||||
}
|
||||
|
||||
public function edit()
|
||||
{
|
||||
$group_model = new GroupModel();
|
||||
|
||||
$roles = $group_model
|
||||
->select('auth_groups.*')
|
||||
->like('name', 'podcasts:' . $this->podcast->id, 'after')
|
||||
->findAll();
|
||||
|
||||
$user_role = $group_model
|
||||
->select('auth_groups.*')
|
||||
->join(
|
||||
'auth_groups_users',
|
||||
'auth_groups_users.group_id = auth_groups.id'
|
||||
)
|
||||
->where('auth_groups_users.user_id', $this->user->id)
|
||||
->like('name', 'podcasts:' . $this->podcast->id, 'after')
|
||||
->first();
|
||||
|
||||
$data = [
|
||||
'podcast' => $this->podcast,
|
||||
'user' => $this->user,
|
||||
'user_role' => $user_role,
|
||||
'roles' => $roles,
|
||||
];
|
||||
|
||||
echo view('admin/contributor/edit', $data);
|
||||
}
|
||||
|
||||
public function attemptEdit()
|
||||
{
|
||||
$authorize = Services::authorization();
|
||||
|
||||
$group_model = new GroupModel();
|
||||
|
||||
$group = $group_model
|
||||
->select('auth_groups.*')
|
||||
->join(
|
||||
'auth_groups_users',
|
||||
'auth_groups_users.group_id = auth_groups.id'
|
||||
)
|
||||
->where('user_id', $this->user->id)
|
||||
->like('name', 'podcasts:' . $this->podcast->id, 'after')
|
||||
->first();
|
||||
|
||||
$authorize->removeUserFromGroup(
|
||||
(int) $this->user->id,
|
||||
(int) $group->id
|
||||
);
|
||||
|
||||
$authorize->addUserToGroup(
|
||||
(int) $this->user->id,
|
||||
(int) $this->request->getPost('role')
|
||||
);
|
||||
|
||||
return redirect()->route('contributor_list', [$this->podcast->id]);
|
||||
}
|
||||
|
||||
public function remove()
|
||||
{
|
||||
$authorize = Services::authorization();
|
||||
|
||||
$group_model = new GroupModel();
|
||||
|
||||
$group = $group_model
|
||||
->select('auth_groups.*')
|
||||
->join(
|
||||
'auth_groups_users',
|
||||
'auth_groups_users.group_id = auth_groups.id'
|
||||
)
|
||||
->like('name', 'podcasts:' . $this->podcast->id, 'after')
|
||||
->where('user_id', $this->user->id)
|
||||
->first();
|
||||
|
||||
$authorize->removeUserFromGroup(
|
||||
(int) $this->user->id,
|
||||
(int) $group->id
|
||||
);
|
||||
|
||||
(new PodcastModel())->removeContributorFromPodcast(
|
||||
$this->user->id,
|
||||
$this->podcast->id
|
||||
);
|
||||
|
||||
return redirect()->route('contributor_list', [$this->podcast->id]);
|
||||
}
|
||||
}
|
|
@ -17,23 +17,52 @@ class Episode extends BaseController
|
|||
|
||||
public function _remap($method, ...$params)
|
||||
{
|
||||
switch ($method) {
|
||||
case 'list':
|
||||
if (
|
||||
!has_permission('episodes-list') ||
|
||||
!has_permission("podcasts:$params[0]:episodes-list")
|
||||
) {
|
||||
throw new \RuntimeException(
|
||||
lang('Auth.notEnoughPrivilege')
|
||||
);
|
||||
}
|
||||
case 'edit':
|
||||
if (
|
||||
!has_permission('episodes-edit') ||
|
||||
!has_permission("podcasts:$params[0]:episodes-edit")
|
||||
) {
|
||||
throw new \RuntimeException(
|
||||
lang('Auth.notEnoughPrivilege')
|
||||
);
|
||||
}
|
||||
case 'delete':
|
||||
if (
|
||||
!has_permission('episodes-delete') ||
|
||||
!has_permission("podcasts:$params[0]:episodes-delete")
|
||||
) {
|
||||
throw new \RuntimeException(
|
||||
lang('Auth.notEnoughPrivilege')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$podcast_model = new PodcastModel();
|
||||
|
||||
$this->podcast = $podcast_model->where('name', $params[0])->first();
|
||||
$this->podcast = $podcast_model->find($params[0]);
|
||||
|
||||
if (count($params) > 1) {
|
||||
$episode_model = new EpisodeModel();
|
||||
if (
|
||||
!($episode = $episode_model
|
||||
!($this->episode = $episode_model
|
||||
->where([
|
||||
'podcast_id' => $this->podcast->id,
|
||||
'slug' => $params[1],
|
||||
'id' => $params[1],
|
||||
'podcast_id' => $params[0],
|
||||
])
|
||||
->first())
|
||||
) {
|
||||
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
$this->episode = $episode;
|
||||
}
|
||||
|
||||
return $this->$method();
|
||||
|
@ -41,13 +70,8 @@ class Episode extends BaseController
|
|||
|
||||
public function list()
|
||||
{
|
||||
$episode_model = new EpisodeModel();
|
||||
|
||||
$data = [
|
||||
'podcast' => $this->podcast,
|
||||
'all_podcast_episodes' => $episode_model
|
||||
->where('podcast_id', $this->podcast->id)
|
||||
->find(),
|
||||
];
|
||||
|
||||
return view('admin/episode/list', $data);
|
||||
|
@ -57,105 +81,118 @@ class Episode extends BaseController
|
|||
{
|
||||
helper(['form']);
|
||||
|
||||
if (
|
||||
!$this->validate([
|
||||
'enclosure' => 'uploaded[enclosure]|ext_in[enclosure,mp3,m4a]',
|
||||
'image' =>
|
||||
'uploaded[image]|is_image[image]|ext_in[image,jpg,png]|permit_empty',
|
||||
'title' => 'required',
|
||||
'slug' => 'required|regex_match[[a-zA-Z0-9\-]{1,191}]',
|
||||
'description' => 'required',
|
||||
'type' => 'required',
|
||||
])
|
||||
) {
|
||||
$data = [
|
||||
'podcast' => $this->podcast,
|
||||
];
|
||||
$data = [
|
||||
'podcast' => $this->podcast,
|
||||
];
|
||||
|
||||
echo view('admin/episode/create', $data);
|
||||
} else {
|
||||
$new_episode = new \App\Entities\Episode([
|
||||
'podcast_id' => $this->podcast->id,
|
||||
'title' => $this->request->getVar('title'),
|
||||
'slug' => $this->request->getVar('slug'),
|
||||
'enclosure' => $this->request->getFile('enclosure'),
|
||||
'pub_date' => $this->request->getVar('pub_date'),
|
||||
'description' => $this->request->getVar('description'),
|
||||
'image' => $this->request->getFile('image'),
|
||||
'explicit' => $this->request->getVar('explicit') or false,
|
||||
'number' => $this->request->getVar('episode_number'),
|
||||
'season_number' => $this->request->getVar('season_number'),
|
||||
'type' => $this->request->getVar('type'),
|
||||
'author_name' => $this->request->getVar('author_name'),
|
||||
'author_email' => $this->request->getVar('author_email'),
|
||||
'block' => $this->request->getVar('block') or false,
|
||||
]);
|
||||
echo view('admin/episode/create', $data);
|
||||
}
|
||||
|
||||
$episode_model = new EpisodeModel();
|
||||
$episode_model->save($new_episode);
|
||||
public function attemptCreate()
|
||||
{
|
||||
$rules = [
|
||||
'enclosure' => 'uploaded[enclosure]|ext_in[enclosure,mp3,m4a]',
|
||||
'image' =>
|
||||
'uploaded[image]|is_image[image]|ext_in[image,jpg,png]|permit_empty',
|
||||
];
|
||||
|
||||
return redirect()->route('episode_list', [$this->podcast->name]);
|
||||
if (!$this->validate($rules)) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', $this->validator->getErrors());
|
||||
}
|
||||
|
||||
$new_episode = new \App\Entities\Episode([
|
||||
'podcast_id' => $this->podcast->id,
|
||||
'title' => $this->request->getPost('title'),
|
||||
'slug' => $this->request->getPost('slug'),
|
||||
'enclosure' => $this->request->getFile('enclosure'),
|
||||
'pub_date' => $this->request->getPost('pub_date'),
|
||||
'description' => $this->request->getPost('description'),
|
||||
'image' => $this->request->getFile('image'),
|
||||
'explicit' => (bool) $this->request->getPost('explicit'),
|
||||
'number' => $this->request->getPost('episode_number'),
|
||||
'season_number' => $this->request->getPost('season_number'),
|
||||
'type' => $this->request->getPost('type'),
|
||||
'author_name' => $this->request->getPost('author_name'),
|
||||
'author_email' => $this->request->getPost('author_email'),
|
||||
'block' => (bool) $this->request->getPost('block'),
|
||||
]);
|
||||
|
||||
$episode_model = new EpisodeModel();
|
||||
|
||||
if (!$episode_model->save($new_episode)) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', $episode_model->errors());
|
||||
}
|
||||
|
||||
return redirect()->route('episode_list', [$this->podcast->id]);
|
||||
}
|
||||
|
||||
public function edit()
|
||||
{
|
||||
helper(['form']);
|
||||
|
||||
if (
|
||||
!$this->validate([
|
||||
'enclosure' =>
|
||||
'uploaded[enclosure]|ext_in[enclosure,mp3,m4a]|permit_empty',
|
||||
'image' =>
|
||||
'uploaded[image]|is_image[image]|ext_in[image,jpg,png]|permit_empty',
|
||||
'title' => 'required',
|
||||
'slug' => 'required|regex_match[[a-zA-Z0-9\-]{1,191}]',
|
||||
'description' => 'required',
|
||||
'type' => 'required',
|
||||
])
|
||||
) {
|
||||
$data = [
|
||||
'podcast' => $this->podcast,
|
||||
'episode' => $this->episode,
|
||||
];
|
||||
$data = [
|
||||
'podcast' => $this->podcast,
|
||||
'episode' => $this->episode,
|
||||
];
|
||||
|
||||
echo view('admin/episode/edit', $data);
|
||||
} else {
|
||||
$this->episode->title = $this->request->getVar('title');
|
||||
$this->episode->slug = $this->request->getVar('slug');
|
||||
$this->episode->pub_date = $this->request->getVar('pub_date');
|
||||
$this->episode->description = $this->request->getVar('description');
|
||||
$this->episode->explicit =
|
||||
($this->request->getVar('explicit') or false);
|
||||
$this->episode->number = $this->request->getVar('episode_number');
|
||||
$this->episode->season_number = $this->request->getVar(
|
||||
'season_number'
|
||||
)
|
||||
? $this->request->getVar('season_number')
|
||||
: null;
|
||||
$this->episode->type = $this->request->getVar('type');
|
||||
$this->episode->author_name = $this->request->getVar('author_name');
|
||||
$this->episode->author_email = $this->request->getVar(
|
||||
'author_email'
|
||||
);
|
||||
$this->episode->block = ($this->request->getVar('block') or false);
|
||||
echo view('admin/episode/edit', $data);
|
||||
}
|
||||
|
||||
$enclosure = $this->request->getFile('enclosure');
|
||||
if ($enclosure->isValid()) {
|
||||
$this->episode->enclosure = $this->request->getFile(
|
||||
'enclosure'
|
||||
);
|
||||
}
|
||||
$image = $this->request->getFile('image');
|
||||
if ($image) {
|
||||
$this->episode->image = $this->request->getFile('image');
|
||||
}
|
||||
public function attemptEdit()
|
||||
{
|
||||
$rules = [
|
||||
'enclosure' =>
|
||||
'uploaded[enclosure]|ext_in[enclosure,mp3,m4a]|permit_empty',
|
||||
'image' =>
|
||||
'uploaded[image]|is_image[image]|ext_in[image,jpg,png]|permit_empty',
|
||||
];
|
||||
|
||||
$episode_model = new EpisodeModel();
|
||||
$episode_model->save($this->episode);
|
||||
|
||||
return redirect()->route('episode_list', [$this->podcast->name]);
|
||||
if (!$this->validate($rules)) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', $this->validator->getErrors());
|
||||
}
|
||||
|
||||
$this->episode->title = $this->request->getPost('title');
|
||||
$this->episode->slug = $this->request->getPost('slug');
|
||||
$this->episode->pub_date = $this->request->getPost('pub_date');
|
||||
$this->episode->description = $this->request->getPost('description');
|
||||
$this->episode->explicit = (bool) $this->request->getPost('explicit');
|
||||
$this->episode->number = $this->request->getPost('episode_number');
|
||||
$this->episode->season_number = $this->request->getPost('season_number')
|
||||
? $this->request->getPost('season_number')
|
||||
: null;
|
||||
$this->episode->type = $this->request->getPost('type');
|
||||
$this->episode->author_name = $this->request->getPost('author_name');
|
||||
$this->episode->author_email = $this->request->getPost('author_email');
|
||||
$this->episode->block = (bool) $this->request->getPost('block');
|
||||
|
||||
$enclosure = $this->request->getFile('enclosure');
|
||||
if ($enclosure->isValid()) {
|
||||
$this->episode->enclosure = $enclosure;
|
||||
}
|
||||
$image = $this->request->getFile('image');
|
||||
if ($image) {
|
||||
$this->episode->image = $image;
|
||||
}
|
||||
|
||||
$episode_model = new EpisodeModel();
|
||||
|
||||
if (!$episode_model->save($this->episode)) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', $episode_model->errors());
|
||||
}
|
||||
|
||||
return redirect()->route('episode_list', [$this->podcast->id]);
|
||||
}
|
||||
|
||||
public function delete()
|
||||
|
@ -163,6 +200,6 @@ class Episode extends BaseController
|
|||
$episode_model = new EpisodeModel();
|
||||
$episode_model->delete($this->episode->id);
|
||||
|
||||
return redirect()->route('episode_list', [$this->podcast->name]);
|
||||
return redirect()->route('episode_list', [$this->podcast->id]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Entities\UserPodcast;
|
||||
use App\Models\CategoryModel;
|
||||
use App\Models\LanguageModel;
|
||||
use App\Models\PodcastModel;
|
||||
|
@ -18,18 +17,59 @@ class Podcast extends BaseController
|
|||
public function _remap($method, ...$params)
|
||||
{
|
||||
if (count($params) > 0) {
|
||||
switch ($method) {
|
||||
case 'edit':
|
||||
if (
|
||||
!has_permission('podcasts-edit') ||
|
||||
!has_permission("podcasts:$params[0]-edit")
|
||||
) {
|
||||
throw new \RuntimeException(
|
||||
lang('Auth.notEnoughPrivilege')
|
||||
);
|
||||
}
|
||||
case 'delete':
|
||||
if (
|
||||
!has_permission('podcasts-delete') ||
|
||||
!has_permission("podcasts:$params[0]-delete")
|
||||
) {
|
||||
throw new \RuntimeException(
|
||||
lang('Auth.notEnoughPrivilege')
|
||||
);
|
||||
}
|
||||
case 'listContributors':
|
||||
case 'addContributor':
|
||||
case 'editContributor':
|
||||
case 'deleteContributor':
|
||||
if (
|
||||
!has_permission('podcasts-manage_contributors') ||
|
||||
!has_permission(
|
||||
"podcasts:$params[0]-manage_contributors"
|
||||
)
|
||||
) {
|
||||
throw new \RuntimeException(
|
||||
lang('Auth.notEnoughPrivilege')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$podcast_model = new PodcastModel();
|
||||
if (
|
||||
!($podcast = $podcast_model->where('name', $params[0])->first())
|
||||
) {
|
||||
if (!($this->podcast = $podcast_model->find($params[0]))) {
|
||||
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
$this->podcast = $podcast;
|
||||
}
|
||||
|
||||
return $this->$method();
|
||||
}
|
||||
|
||||
public function myPodcasts()
|
||||
{
|
||||
$data = [
|
||||
'all_podcasts' => (new PodcastModel())->getUserPodcasts(user()->id),
|
||||
];
|
||||
|
||||
return view('admin/podcast/list', $data);
|
||||
}
|
||||
|
||||
public function list()
|
||||
{
|
||||
$podcast_model = new PodcastModel();
|
||||
|
@ -42,133 +82,141 @@ class Podcast extends BaseController
|
|||
public function create()
|
||||
{
|
||||
helper(['form', 'misc']);
|
||||
$podcast_model = new PodcastModel();
|
||||
|
||||
if (
|
||||
!$this->validate([
|
||||
'title' => 'required',
|
||||
'name' => 'required|regex_match[[a-zA-Z0-9\_]{1,191}]',
|
||||
'description' => 'required|max_length[4000]',
|
||||
'image' =>
|
||||
'uploaded[image]|is_image[image]|ext_in[image,jpg,png]',
|
||||
'owner_email' => 'required|valid_email',
|
||||
'type' => 'required',
|
||||
])
|
||||
) {
|
||||
$languageModel = new LanguageModel();
|
||||
$categoryModel = new CategoryModel();
|
||||
$data = [
|
||||
'languages' => $languageModel->findAll(),
|
||||
'categories' => $categoryModel->findAll(),
|
||||
'browser_lang' => get_browser_language(
|
||||
$this->request->getServer('HTTP_ACCEPT_LANGUAGE')
|
||||
),
|
||||
];
|
||||
$languageModel = new LanguageModel();
|
||||
$categoryModel = new CategoryModel();
|
||||
$data = [
|
||||
'languages' => $languageModel->findAll(),
|
||||
'categories' => $categoryModel->findAll(),
|
||||
'browser_lang' => get_browser_language(
|
||||
$this->request->getServer('HTTP_ACCEPT_LANGUAGE')
|
||||
),
|
||||
];
|
||||
|
||||
echo view('admin/podcast/create', $data);
|
||||
} else {
|
||||
$podcast = new \App\Entities\Podcast([
|
||||
'title' => $this->request->getVar('title'),
|
||||
'name' => $this->request->getVar('name'),
|
||||
'description' => $this->request->getVar('description'),
|
||||
'episode_description_footer' => $this->request->getVar(
|
||||
'episode_description_footer'
|
||||
),
|
||||
'image' => $this->request->getFile('image'),
|
||||
'language' => $this->request->getVar('language'),
|
||||
'category' => $this->request->getVar('category'),
|
||||
'explicit' => $this->request->getVar('explicit') or false,
|
||||
'author_name' => $this->request->getVar('author_name'),
|
||||
'author_email' => $this->request->getVar('author_email'),
|
||||
'owner_name' => $this->request->getVar('owner_name'),
|
||||
'owner_email' => $this->request->getVar('owner_email'),
|
||||
'type' => $this->request->getVar('type'),
|
||||
'copyright' => $this->request->getVar('copyright'),
|
||||
'block' => $this->request->getVar('block') or false,
|
||||
'complete' => $this->request->getVar('complete') or false,
|
||||
'custom_html_head' => $this->request->getVar(
|
||||
'custom_html_head'
|
||||
),
|
||||
]);
|
||||
echo view('admin/podcast/create', $data);
|
||||
}
|
||||
|
||||
$db = \Config\Database::connect();
|
||||
public function attemptCreate()
|
||||
{
|
||||
$rules = [
|
||||
'image' => 'uploaded[image]|is_image[image]|ext_in[image,jpg,png]',
|
||||
];
|
||||
|
||||
$db->transStart();
|
||||
|
||||
$new_podcast_id = $podcast_model->insert($podcast, true);
|
||||
|
||||
$user_podcast_model = new \App\Models\UserPodcastModel();
|
||||
$user_podcast_model->save([
|
||||
'user_id' => user()->id,
|
||||
'podcast_id' => $new_podcast_id,
|
||||
]);
|
||||
|
||||
$db->transComplete();
|
||||
|
||||
return redirect()->route('podcast_list', [$podcast->name]);
|
||||
if (!$this->validate($rules)) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', $this->validator->getErrors());
|
||||
}
|
||||
|
||||
$podcast = new \App\Entities\Podcast([
|
||||
'title' => $this->request->getPost('title'),
|
||||
'name' => $this->request->getPost('name'),
|
||||
'description' => $this->request->getPost('description'),
|
||||
'episode_description_footer' => $this->request->getPost(
|
||||
'episode_description_footer'
|
||||
),
|
||||
'image' => $this->request->getFile('image'),
|
||||
'language' => $this->request->getPost('language'),
|
||||
'category' => $this->request->getPost('category'),
|
||||
'explicit' => (bool) $this->request->getPost('explicit'),
|
||||
'author_name' => $this->request->getPost('author_name'),
|
||||
'author_email' => $this->request->getPost('author_email'),
|
||||
'owner' => user(),
|
||||
'owner_name' => $this->request->getPost('owner_name'),
|
||||
'owner_email' => $this->request->getPost('owner_email'),
|
||||
'type' => $this->request->getPost('type'),
|
||||
'copyright' => $this->request->getPost('copyright'),
|
||||
'block' => (bool) $this->request->getPost('block'),
|
||||
'complete' => (bool) $this->request->getPost('complete'),
|
||||
'custom_html_head' => $this->request->getPost('custom_html_head'),
|
||||
]);
|
||||
|
||||
$podcast_model = new PodcastModel();
|
||||
$db = \Config\Database::connect();
|
||||
|
||||
$db->transStart();
|
||||
|
||||
if (!($new_podcast_id = $podcast_model->insert($podcast, true))) {
|
||||
$db->transComplete();
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', $podcast_model->errors());
|
||||
}
|
||||
|
||||
$podcast_model->addContributorToPodcast(user()->id, $new_podcast_id);
|
||||
|
||||
$db->transComplete();
|
||||
|
||||
return redirect()->route('podcast_list');
|
||||
}
|
||||
|
||||
public function edit()
|
||||
{
|
||||
helper(['form', 'misc']);
|
||||
helper('form');
|
||||
|
||||
if (
|
||||
!$this->validate([
|
||||
'title' => 'required',
|
||||
'name' => 'required|regex_match[[a-zA-Z0-9\_]{1,191}]',
|
||||
'description' => 'required|max_length[4000]',
|
||||
'image' =>
|
||||
'uploaded[image]|is_image[image]|ext_in[image,jpg,png]|permit_empty',
|
||||
'owner_email' => 'required|valid_email',
|
||||
'type' => 'required',
|
||||
])
|
||||
) {
|
||||
$languageModel = new LanguageModel();
|
||||
$categoryModel = new CategoryModel();
|
||||
$data = [
|
||||
'podcast' => $this->podcast,
|
||||
'languages' => $languageModel->findAll(),
|
||||
'categories' => $categoryModel->findAll(),
|
||||
];
|
||||
$languageModel = new LanguageModel();
|
||||
$categoryModel = new CategoryModel();
|
||||
$data = [
|
||||
'podcast' => $this->podcast,
|
||||
'languages' => $languageModel->findAll(),
|
||||
'categories' => $categoryModel->findAll(),
|
||||
];
|
||||
|
||||
echo view('admin/podcast/edit', $data);
|
||||
} else {
|
||||
$this->podcast->title = $this->request->getVar('title');
|
||||
$this->podcast->name = $this->request->getVar('name');
|
||||
$this->podcast->description = $this->request->getVar('description');
|
||||
$this->podcast->episode_description_footer = $this->request->getVar(
|
||||
'episode_description_footer'
|
||||
);
|
||||
echo view('admin/podcast/edit', $data);
|
||||
}
|
||||
|
||||
$image = $this->request->getFile('image');
|
||||
if ($image->isValid()) {
|
||||
$this->podcast->image = $this->request->getFile('image');
|
||||
}
|
||||
$this->podcast->language = $this->request->getVar('language');
|
||||
$this->podcast->category = $this->request->getVar('category');
|
||||
$this->podcast->explicit =
|
||||
($this->request->getVar('explicit') or false);
|
||||
$this->podcast->author_name = $this->request->getVar('author_name');
|
||||
$this->podcast->author_email = $this->request->getVar(
|
||||
'author_email'
|
||||
);
|
||||
$this->podcast->owner_name = $this->request->getVar('owner_name');
|
||||
$this->podcast->owner_email = $this->request->getVar('owner_email');
|
||||
$this->podcast->type = $this->request->getVar('type');
|
||||
$this->podcast->copyright = $this->request->getVar('copyright');
|
||||
$this->podcast->block = ($this->request->getVar('block') or false);
|
||||
$this->podcast->complete =
|
||||
($this->request->getVar('complete') or false);
|
||||
$this->podcast->custom_html_head = $this->request->getVar(
|
||||
'custom_html_head'
|
||||
);
|
||||
public function attemptEdit()
|
||||
{
|
||||
$rules = [
|
||||
'image' =>
|
||||
'uploaded[image]|is_image[image]|ext_in[image,jpg,png]|permit_empty',
|
||||
];
|
||||
|
||||
$podcast_model = new PodcastModel();
|
||||
$podcast_model->save($this->podcast);
|
||||
|
||||
return redirect()->route('podcast_list', [$this->podcast->name]);
|
||||
if (!$this->validate($rules)) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', $this->validator->getErrors());
|
||||
}
|
||||
|
||||
$this->podcast->title = $this->request->getPost('title');
|
||||
$this->podcast->name = $this->request->getPost('name');
|
||||
$this->podcast->description = $this->request->getPost('description');
|
||||
$this->podcast->episode_description_footer = $this->request->getPost(
|
||||
'episode_description_footer'
|
||||
);
|
||||
|
||||
$image = $this->request->getFile('image');
|
||||
if ($image->isValid()) {
|
||||
$this->podcast->image = $image;
|
||||
}
|
||||
$this->podcast->language = $this->request->getPost('language');
|
||||
$this->podcast->category = $this->request->getPost('category');
|
||||
$this->podcast->explicit = (bool) $this->request->getPost('explicit');
|
||||
$this->podcast->author_name = $this->request->getPost('author_name');
|
||||
$this->podcast->author_email = $this->request->getPost('author_email');
|
||||
$this->podcast->owner_name = $this->request->getPost('owner_name');
|
||||
$this->podcast->owner_email = $this->request->getPost('owner_email');
|
||||
$this->podcast->type = $this->request->getPost('type');
|
||||
$this->podcast->copyright = $this->request->getPost('copyright');
|
||||
$this->podcast->block = (bool) $this->request->getPost('block');
|
||||
$this->podcast->complete = (bool) $this->request->getPost('complete');
|
||||
$this->podcast->custom_html_head = $this->request->getPost(
|
||||
'custom_html_head'
|
||||
);
|
||||
|
||||
$podcast_model = new PodcastModel();
|
||||
|
||||
if (!$podcast_model->save($this->podcast)) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', $podcast_model->errors());
|
||||
}
|
||||
|
||||
return redirect()->route('podcast_list');
|
||||
}
|
||||
|
||||
public function delete()
|
||||
|
|
|
@ -17,12 +17,9 @@ class User extends BaseController
|
|||
{
|
||||
if (count($params) > 0) {
|
||||
$user_model = new UserModel();
|
||||
if (
|
||||
!($user = $user_model->where('username', $params[0])->first())
|
||||
) {
|
||||
if (!($this->user = $user_model->find($params[0]))) {
|
||||
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
return $this->$method();
|
||||
|
@ -38,6 +35,11 @@ class User extends BaseController
|
|||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
echo view('admin/user/create');
|
||||
}
|
||||
|
||||
public function attemptCreate()
|
||||
{
|
||||
$user_model = new UserModel();
|
||||
|
||||
|
@ -53,30 +55,33 @@ class User extends BaseController
|
|||
);
|
||||
|
||||
if (!$this->validate($rules)) {
|
||||
echo view('admin/user/create');
|
||||
} else {
|
||||
// Save the user
|
||||
$user = new \Myth\Auth\Entities\User($this->request->getPost());
|
||||
|
||||
// Activate user
|
||||
$user->activate();
|
||||
|
||||
// Force user to reset his password on first connection
|
||||
$user->force_pass_reset = true;
|
||||
$user->generateResetHash();
|
||||
|
||||
if (!$user_model->save($user)) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', $user_model->errors());
|
||||
}
|
||||
|
||||
// Success!
|
||||
return redirect()
|
||||
->route('user_list')
|
||||
->with('message', lang('User.createSuccess'));
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', $this->validator->getErrors());
|
||||
}
|
||||
|
||||
// Save the user
|
||||
$user = new \Myth\Auth\Entities\User($this->request->getPost());
|
||||
|
||||
// Activate user
|
||||
$user->activate();
|
||||
|
||||
// Force user to reset his password on first connection
|
||||
$user->force_pass_reset = true;
|
||||
$user->generateResetHash();
|
||||
|
||||
if (!$user_model->save($user)) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', $user_model->errors());
|
||||
}
|
||||
|
||||
// Success!
|
||||
return redirect()
|
||||
->route('user_list')
|
||||
->with('message', lang('User.createSuccess'));
|
||||
}
|
||||
|
||||
public function forcePassReset()
|
||||
|
|
|
@ -24,7 +24,7 @@ class Episode extends BaseController
|
|||
if (count($params) > 1) {
|
||||
$episode_model = new EpisodeModel();
|
||||
if (
|
||||
!($episode = $episode_model
|
||||
!($this->episode = $episode_model
|
||||
->where([
|
||||
'podcast_id' => $this->podcast->id,
|
||||
'slug' => $params[1],
|
||||
|
@ -33,7 +33,6 @@ class Episode extends BaseController
|
|||
) {
|
||||
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
$this->episode = $episode;
|
||||
}
|
||||
|
||||
return $this->$method();
|
||||
|
|
|
@ -17,11 +17,12 @@ class Podcast extends BaseController
|
|||
if (count($params) > 0) {
|
||||
$podcast_model = new PodcastModel();
|
||||
if (
|
||||
!($podcast = $podcast_model->where('name', $params[0])->first())
|
||||
!($this->podcast = $podcast_model
|
||||
->where('name', $params[0])
|
||||
->first())
|
||||
) {
|
||||
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
$this->podcast = $podcast;
|
||||
}
|
||||
|
||||
return $this->$method();
|
||||
|
|
|
@ -81,17 +81,23 @@ class AddPodcasts extends Migration
|
|||
'Email of the group responsible for creating the show. Show author most often refers to the parent company or network of a podcast, but it can also be used to identify the host(s) if none exists. Author information is especially useful if a company or organization publishes multiple podcasts. Providing this information will allow listeners to see all shows created by the same entity.',
|
||||
'null' => true,
|
||||
],
|
||||
'owner_id' => [
|
||||
'type' => 'INT',
|
||||
'constraint' => 11,
|
||||
'unsigned' => true,
|
||||
'comment' => 'The podcast owner.',
|
||||
],
|
||||
'owner_name' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 1024,
|
||||
'owner_name' =>
|
||||
'comment' =>
|
||||
'The podcast owner name. Note: The owner information is for administrative communication about the podcast and isn’t displayed in Apple Podcasts.',
|
||||
'null' => true,
|
||||
],
|
||||
'owner_email' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 1024,
|
||||
'owner_email' =>
|
||||
'comment' =>
|
||||
'The podcast owner email address. Note: The owner information is for administrative communication about the podcast and isn’t displayed in Apple Podcasts. Please make sure the email address is active and monitored.',
|
||||
'null' => true,
|
||||
],
|
||||
|
@ -147,6 +153,7 @@ class AddPodcasts extends Migration
|
|||
],
|
||||
]);
|
||||
$this->forge->addKey('id', true);
|
||||
$this->forge->addForeignKey('owner_id', 'users', 'id');
|
||||
$this->forge->createTable('podcasts');
|
||||
}
|
||||
|
||||
|
|
|
@ -17,12 +17,6 @@ class AddUsersPodcasts extends Migration
|
|||
public function up()
|
||||
{
|
||||
$this->forge->addField([
|
||||
'id' => [
|
||||
'type' => 'BIGINT',
|
||||
'constraint' => 20,
|
||||
'unsigned' => true,
|
||||
'auto_increment' => true,
|
||||
],
|
||||
'user_id' => [
|
||||
'type' => 'INT',
|
||||
'constraint' => 11,
|
||||
|
@ -34,8 +28,7 @@ class AddUsersPodcasts extends Migration
|
|||
'unsigned' => true,
|
||||
],
|
||||
]);
|
||||
$this->forge->addPrimaryKey('id');
|
||||
$this->forge->addUniqueKey(['user_id', 'podcast_id']);
|
||||
$this->forge->addPrimaryKey(['user_id', 'podcast_id']);
|
||||
$this->forge->addForeignKey('user_id', 'users', 'id');
|
||||
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id');
|
||||
$this->forge->createTable('users_podcasts');
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
<?php
|
||||
/**
|
||||
* Class PermissionSeeder
|
||||
* Inserts permissions
|
||||
*
|
||||
* @copyright 2020 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Database\Seeds;
|
||||
|
||||
use CodeIgniter\Database\Seeder;
|
||||
|
||||
class AuthSeeder extends Seeder
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
helper('auth');
|
||||
|
||||
$groups = [['id' => 1, 'name' => 'superadmin', 'description' => '']];
|
||||
|
||||
/** Build permissions array as a list of:
|
||||
*
|
||||
* ```
|
||||
* context => [
|
||||
* [action, description],
|
||||
* [action, description],
|
||||
* ...
|
||||
* ]
|
||||
* ```
|
||||
*/
|
||||
$permissions = [
|
||||
'users' => [
|
||||
['name' => 'create', 'description' => 'Create a user'],
|
||||
['name' => 'list', 'description' => 'List all users'],
|
||||
[
|
||||
'name' => 'manage_authorizations',
|
||||
'description' =>
|
||||
'Add or remove roles/permissions to a user',
|
||||
],
|
||||
[
|
||||
'name' => 'manage_bans',
|
||||
'description' => 'Ban / unban a user',
|
||||
],
|
||||
[
|
||||
'name' => 'force_pass_reset',
|
||||
'description' =>
|
||||
'Force a user to update his password upon next login',
|
||||
],
|
||||
[
|
||||
'name' => 'delete',
|
||||
'description' =>
|
||||
'Delete user without removing him from database',
|
||||
],
|
||||
[
|
||||
'name' => 'delete_permanently',
|
||||
'description' =>
|
||||
'Delete all occurrences of a user from the database',
|
||||
],
|
||||
],
|
||||
'podcasts' => [
|
||||
['name' => 'create', 'description' => 'Add a new podcast'],
|
||||
[
|
||||
'name' => 'list',
|
||||
'description' => 'List all podcasts and their episodes',
|
||||
],
|
||||
['name' => 'edit', 'description' => 'Edit any podcast'],
|
||||
[
|
||||
'name' => 'manage_contributors',
|
||||
'description' => 'Add / remove contributors to a podcast',
|
||||
],
|
||||
[
|
||||
'name' => 'manage_publication',
|
||||
'description' => 'Publish / unpublish a podcast',
|
||||
],
|
||||
[
|
||||
'name' => 'delete',
|
||||
'description' =>
|
||||
'Delete a podcast without removing it from database',
|
||||
],
|
||||
[
|
||||
'name' => 'delete_permanently',
|
||||
'description' => 'Delete any podcast from the database',
|
||||
],
|
||||
],
|
||||
'episodes' => [
|
||||
[
|
||||
'name' => 'list',
|
||||
'description' => 'List all episodes of any podcast',
|
||||
],
|
||||
[
|
||||
'name' => 'create',
|
||||
'description' => 'Add a new episode to any podcast',
|
||||
],
|
||||
['name' => 'edit', 'description' => 'Edit any podcast episode'],
|
||||
[
|
||||
'name' => 'manage_publications',
|
||||
'description' => 'Publish / unpublish any podcast episode',
|
||||
],
|
||||
[
|
||||
'name' => 'delete',
|
||||
'description' =>
|
||||
'Delete any podcast episode without removing it from database',
|
||||
],
|
||||
[
|
||||
'name' => 'delete_permanently',
|
||||
'description' => 'Delete any podcast episode from database',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
// Map permissions to a format the `auth_permissions` table expects
|
||||
$data_permissions = [];
|
||||
$data_groups_permissions = [];
|
||||
$permission_id = 0;
|
||||
foreach ($permissions as $context => $actions) {
|
||||
foreach ($actions as $action) {
|
||||
array_push($data_permissions, [
|
||||
'id' => ++$permission_id,
|
||||
'name' => get_permission($context, $action['name']),
|
||||
'description' => $action['description'],
|
||||
]);
|
||||
|
||||
// add all permissions to superadmin
|
||||
array_push($data_groups_permissions, [
|
||||
'group_id' => 1,
|
||||
'permission_id' => $permission_id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->db->table('auth_permissions')->insertBatch($data_permissions);
|
||||
$this->db->table('auth_groups')->insertBatch($groups);
|
||||
$this->db
|
||||
->table('auth_groups_permissions')
|
||||
->insertBatch($data_groups_permissions);
|
||||
|
||||
// TODO: Remove superadmin user as it is used for testing purposes
|
||||
$this->db->table('users')->insert([
|
||||
'id' => 1,
|
||||
'username' => 'admin',
|
||||
'email' => 'admin@castopod.com',
|
||||
'password_hash' =>
|
||||
// password: AGUehL3P
|
||||
'$2y$10$TXJEHX/djW8jtzgpDVf7dOOCGo5rv1uqtAYWdwwwkttQcDkAeB2.6',
|
||||
'active' => 1,
|
||||
]);
|
||||
$this->db
|
||||
->table('auth_groups_users')
|
||||
->insert(['group_id' => 1, 'user_id' => 1]);
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Class UserSeeder
|
||||
* Inserts 'admin' user in users table for testing purposes
|
||||
*
|
||||
* @copyright 2020 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Database\Seeds;
|
||||
|
||||
use CodeIgniter\Database\Seeder;
|
||||
|
||||
class UserSeeder extends Seeder
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$data = [
|
||||
'username' => 'admin',
|
||||
'email' => 'admin@castopod.com',
|
||||
'password_hash' =>
|
||||
// password: AGUehL3P
|
||||
'$2y$10$TXJEHX/djW8jtzgpDVf7dOOCGo5rv1uqtAYWdwwwkttQcDkAeB2.6',
|
||||
'active' => 1,
|
||||
];
|
||||
|
||||
$this->db->table('users')->insert($data);
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ namespace App\Entities;
|
|||
|
||||
use App\Models\EpisodeModel;
|
||||
use CodeIgniter\Entity;
|
||||
use Myth\Auth\Models\UserModel;
|
||||
|
||||
class Podcast extends Entity
|
||||
{
|
||||
|
@ -17,6 +18,8 @@ class Podcast extends Entity
|
|||
protected $image_media_path;
|
||||
protected $image_url;
|
||||
protected $episodes;
|
||||
protected $owner;
|
||||
protected $contributors;
|
||||
|
||||
protected $casts = [
|
||||
'id' => 'integer',
|
||||
|
@ -29,6 +32,7 @@ class Podcast extends Entity
|
|||
'explicit' => 'boolean',
|
||||
'author_name' => '?string',
|
||||
'author_email' => '?string',
|
||||
'owner_id' => 'integer',
|
||||
'owner_name' => '?string',
|
||||
'owner_email' => '?string',
|
||||
'type' => 'string',
|
||||
|
@ -92,7 +96,7 @@ class Podcast extends Entity
|
|||
);
|
||||
}
|
||||
|
||||
if (empty($this->permissions)) {
|
||||
if (empty($this->episodes)) {
|
||||
$this->episodes = (new EpisodeModel())->getPodcastEpisodes(
|
||||
$this->id
|
||||
);
|
||||
|
@ -100,4 +104,40 @@ class Podcast extends Entity
|
|||
|
||||
return $this->episodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the podcast owner
|
||||
*
|
||||
* @return \Myth\Auth\Entities\User
|
||||
*/
|
||||
public function getOwner()
|
||||
{
|
||||
if (empty($this->id)) {
|
||||
throw new \RuntimeException(
|
||||
'Podcast must be created before getting owner.'
|
||||
);
|
||||
}
|
||||
|
||||
if (empty($this->owner)) {
|
||||
$this->owner = (new UserModel())->find($this->owner_id);
|
||||
}
|
||||
|
||||
return $this->owner;
|
||||
}
|
||||
|
||||
public function setOwner(\Myth\Auth\Entities\User $user)
|
||||
{
|
||||
$this->attributes['owner_id'] = $user->id;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getContributors()
|
||||
{
|
||||
return (new UserModel())
|
||||
->select('users.*')
|
||||
->join('users_podcasts', 'users_podcasts.user_id = users.id')
|
||||
->where('users_podcasts.podcast_id', $this->attributes['id'])
|
||||
->findAll();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright 2020 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Entities;
|
||||
|
||||
use CodeIgniter\Entity;
|
||||
|
||||
class UserPodcast extends Entity
|
||||
{
|
||||
protected $casts = [
|
||||
'user_id' => 'integer',
|
||||
'podcast_id' => 'integer',
|
||||
];
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright 2020 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
/**
|
||||
* Gets the permission name by concatenating the context and action
|
||||
*
|
||||
* @param string $context
|
||||
* @param string $action
|
||||
*
|
||||
* @return string permission name
|
||||
*/
|
||||
function get_permission($context, $action)
|
||||
{
|
||||
return $context . '-' . $action;
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<?
|
||||
/**
|
||||
* @copyright 2020 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
return [
|
||||
'podcast_contributors' => 'Podcast contributors',
|
||||
'add' => 'Add contributor',
|
||||
'add_contributor' => 'Add a contributor for {0}',
|
||||
'edit_role' => 'Update role for {0}',
|
||||
'edit' => 'Edit',
|
||||
'remove' => 'Remove',
|
||||
'form' => [
|
||||
'user' => 'User',
|
||||
'role' => 'Role',
|
||||
'submit_add' => 'Add contributor',
|
||||
'submit_edit' => 'Update role'
|
||||
]
|
||||
];
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
return [
|
||||
'all_podcast_episodes' => 'All podcast episodes',
|
||||
'create_one' => 'Add a new one',
|
||||
'back_to_podcast' => 'Go back to podcast',
|
||||
'edit' => 'Edit',
|
||||
'delete' => 'Delete',
|
||||
|
|
|
@ -7,5 +7,6 @@
|
|||
|
||||
return [
|
||||
'passwordChangeSuccess' => 'Password has been successfully changed!',
|
||||
'changePassword' => 'Change my password'
|
||||
'changePassword' => 'Change my password',
|
||||
'info' => 'My account info'
|
||||
];
|
||||
|
|
|
@ -8,13 +8,13 @@
|
|||
return [
|
||||
'all_podcasts' => 'All podcasts',
|
||||
'no_podcast' => 'No podcast found!',
|
||||
'create_one' => 'Add a new one',
|
||||
'create' => 'Create a Podcast',
|
||||
'new_episode' => 'New Episode',
|
||||
'feed' => 'RSS feed',
|
||||
'edit' => 'Edit',
|
||||
'delete' => 'Delete',
|
||||
'see_episodes' => 'See episodes',
|
||||
'see_contributors' => 'See contributors',
|
||||
'goto_page' => 'Go to page',
|
||||
'form' => [
|
||||
'title' => 'Title',
|
||||
|
|
|
@ -10,11 +10,13 @@ return [
|
|||
'forcePassResetSuccess' => 'The user will be prompted with a password reset during his next login attempt.',
|
||||
'banSuccess' => 'User has been banned.',
|
||||
'unbanSuccess' => 'User has been unbanned.',
|
||||
'deleteSuccess' => 'User has been deleted.',
|
||||
'forcePassReset' => 'Force pass reset',
|
||||
'ban' => 'Ban',
|
||||
'unban' => 'Unban',
|
||||
'delete' => 'Delete',
|
||||
'create' => 'Create a user',
|
||||
'all_users' => 'All users',
|
||||
'form' => [
|
||||
'email' => 'Email',
|
||||
'username' => 'Username',
|
||||
|
|
|
@ -19,11 +19,8 @@ class EpisodeModel extends Model
|
|||
'title',
|
||||
'slug',
|
||||
'enclosure_uri',
|
||||
'enclosure_length',
|
||||
'enclosure_type',
|
||||
'pub_date',
|
||||
'description',
|
||||
'duration',
|
||||
'image_uri',
|
||||
'explicit',
|
||||
'number',
|
||||
|
@ -39,6 +36,21 @@ class EpisodeModel extends Model
|
|||
protected $useSoftDeletes = true;
|
||||
protected $useTimestamps = true;
|
||||
|
||||
protected $validationRules = [
|
||||
'podcast_id' => 'required',
|
||||
'title' => 'required',
|
||||
'slug' => 'required|regex_match[/^[a-zA-Z0-9\-]{1,191}$/]',
|
||||
'enclosure_uri' => 'required',
|
||||
'pub_date' => 'required|valid_date',
|
||||
'description' => 'required',
|
||||
'image_uri' => 'required',
|
||||
'number' => 'required',
|
||||
'season_number' => 'required',
|
||||
'author_email' => 'valid_email|permit_empty',
|
||||
'type' => 'required',
|
||||
];
|
||||
protected $validationMessages = [];
|
||||
|
||||
protected $afterInsert = ['writeEnclosureMetadata', 'clearCache'];
|
||||
protected $afterUpdate = ['writeEnclosureMetadata', 'clearCache'];
|
||||
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
use Myth\Auth\Authorization\GroupModel;
|
||||
use Myth\Auth\Config\Services;
|
||||
|
||||
class PodcastModel extends Model
|
||||
{
|
||||
|
@ -26,6 +28,7 @@ class PodcastModel extends Model
|
|||
'explicit',
|
||||
'author_name',
|
||||
'author_email',
|
||||
'owner_id',
|
||||
'owner_name',
|
||||
'owner_email',
|
||||
'type',
|
||||
|
@ -40,8 +43,60 @@ class PodcastModel extends Model
|
|||
|
||||
protected $useTimestamps = true;
|
||||
|
||||
protected $afterInsert = ['clearCache'];
|
||||
protected $validationRules = [
|
||||
'title' => 'required',
|
||||
'name' =>
|
||||
'required|regex_match[/^[a-zA-Z0-9\_]{1,191}$/]|is_unique[podcasts.name,id,{id}]',
|
||||
'description' => 'required',
|
||||
'image_uri' => 'required',
|
||||
'language' => 'required',
|
||||
'category' => 'required',
|
||||
'author_email' => 'valid_email|permit_empty',
|
||||
'owner_id' => 'required',
|
||||
'owner_email' => 'required|valid_email',
|
||||
'type' => 'required',
|
||||
];
|
||||
protected $validationMessages = [];
|
||||
|
||||
protected $afterInsert = ['clearCache', 'createPodcastPermissions'];
|
||||
protected $afterUpdate = ['clearCache'];
|
||||
protected $beforeDelete = ['clearCache'];
|
||||
|
||||
/**
|
||||
* Gets all the podcasts a given user is contributing to
|
||||
*
|
||||
* @param int $user_id
|
||||
*
|
||||
* @return \App\Entities\Podcast[] podcasts
|
||||
*/
|
||||
public function getUserPodcasts($user_id)
|
||||
{
|
||||
return $this->select('podcasts.*')
|
||||
->join('users_podcasts', 'users_podcasts.podcast_id = podcasts.id')
|
||||
->where('users_podcasts.user_id', $user_id)
|
||||
->findAll();
|
||||
}
|
||||
|
||||
public function addContributorToPodcast($user_id, $podcast_id)
|
||||
{
|
||||
$data = [
|
||||
'user_id' => (int) $user_id,
|
||||
'podcast_id' => (int) $podcast_id,
|
||||
];
|
||||
|
||||
return $this->db->table('users_podcasts')->insert($data);
|
||||
}
|
||||
|
||||
public function removeContributorFromPodcast($user_id, $podcast_id)
|
||||
{
|
||||
return $this->db
|
||||
->table('users_podcasts')
|
||||
->where([
|
||||
'user_id' => $user_id,
|
||||
'podcast_id' => $podcast_id,
|
||||
])
|
||||
->delete();
|
||||
}
|
||||
|
||||
protected function clearCache(array $data)
|
||||
{
|
||||
|
@ -56,5 +111,95 @@ class PodcastModel extends Model
|
|||
// foreach ($podcast->episodes as $episode) {
|
||||
// $cache->delete(md5($episode->link));
|
||||
// }
|
||||
|
||||
$data['podcast'] = $podcast;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function createPodcastPermissions(array $data)
|
||||
{
|
||||
$authorize = Services::authorization();
|
||||
|
||||
$podcast = $data['podcast'];
|
||||
|
||||
$podcast_permissions = [
|
||||
'podcasts:' . $podcast->id => [
|
||||
[
|
||||
'name' => 'edit',
|
||||
'description' => "Edit the $podcast->name podcast",
|
||||
],
|
||||
[
|
||||
'name' => 'delete',
|
||||
'description' => "Delete the $podcast->name podcast without removing it from the database",
|
||||
],
|
||||
[
|
||||
'name' => 'delete_permanently',
|
||||
'description' => "Delete the $podcast->name podcast from the database",
|
||||
],
|
||||
[
|
||||
'name' => 'manage_contributors',
|
||||
'description' => "Add / remove contributors to the $podcast->name podcast and edit their roles",
|
||||
],
|
||||
[
|
||||
'name' => 'manage_publication',
|
||||
'description' => "Publish / unpublish $podcast->name",
|
||||
],
|
||||
],
|
||||
'podcasts:' . $podcast->id . ':episodes' => [
|
||||
[
|
||||
'name' => 'list',
|
||||
'description' => "List all episodes of the $podcast->name podcast",
|
||||
],
|
||||
[
|
||||
'name' => 'create',
|
||||
'description' => "Add new episodes for the $podcast->name podcast",
|
||||
],
|
||||
[
|
||||
'name' => 'edit',
|
||||
'description' => "Edit an episode of the $podcast->name podcast",
|
||||
],
|
||||
[
|
||||
'name' => 'delete',
|
||||
'description' => "Delete an episode of the $podcast->name podcast without removing it from the database",
|
||||
],
|
||||
[
|
||||
'name' => 'delete_permanently',
|
||||
'description' => "Delete all occurrences of an episode of the $podcast->name podcast from the database",
|
||||
],
|
||||
[
|
||||
'name' => 'manage_publications',
|
||||
'description' => "Publish / unpublish episodes of the $podcast->name podcast",
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$group_model = new GroupModel();
|
||||
$owner_group_id = $group_model->insert(
|
||||
[
|
||||
'name' => "podcasts:$podcast->id" . '_owner',
|
||||
'description' => "The owner of the $podcast->name podcast",
|
||||
],
|
||||
true
|
||||
);
|
||||
|
||||
// add podcast owner to owner group
|
||||
$authorize->addUserToGroup($podcast->owner_id, $owner_group_id);
|
||||
|
||||
foreach ($podcast_permissions as $context => $actions) {
|
||||
foreach ($actions as $action) {
|
||||
$permission_id = $authorize->createPermission(
|
||||
get_permission($context, $action['name']),
|
||||
$action['description']
|
||||
);
|
||||
|
||||
$authorize->addPermissionToGroup(
|
||||
$permission_id,
|
||||
$owner_group_id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright 2020 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
|
||||
class UserPodcastModel extends Model
|
||||
{
|
||||
protected $table = 'users_podcasts';
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
protected $allowedFields = ['user_id', 'podcast_id'];
|
||||
|
||||
protected $returnType = 'App\Entities\UserPodcast';
|
||||
protected $useSoftDeletes = false;
|
||||
|
||||
protected $useTimestamps = false;
|
||||
}
|
|
@ -1,17 +1,17 @@
|
|||
<?php if (session()->has('message')): ?>
|
||||
<div class="px-4 py-2 font-semibold text-green-900 bg-green-200 border border-green-700">
|
||||
<div class="px-4 py-2 mb-4 font-semibold text-green-900 bg-green-200 border border-green-700">
|
||||
<?= session('message') ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (session()->has('error')): ?>
|
||||
<div class="px-4 py-2 font-semibold text-red-900 bg-red-200 border border-red-700">
|
||||
<div class="px-4 py-2 mb-4 font-semibold text-red-900 bg-red-200 border border-red-700">
|
||||
<?= session('error') ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (session()->has('errors')): ?>
|
||||
<ul class="px-4 py-2 font-semibold text-red-900 bg-red-200 border border-red-700">
|
||||
<ul class="px-4 py-2 mb-4 font-semibold text-red-900 bg-red-200 border border-red-700">
|
||||
<?php foreach (session('errors') as $error): ?>
|
||||
<li><?= $error ?></li>
|
||||
<?php endforeach; ?>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Castopod</title>
|
||||
<title>Castopod Admin</title>
|
||||
<meta name="description" content="Castopod is an open-source hosting platform made for podcasters who want engage and interact with their audience.">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="shortcut icon" type="image/png" href="/favicon.ico" />
|
||||
|
@ -12,9 +12,12 @@
|
|||
|
||||
<body class="flex flex-col min-h-screen mx-auto">
|
||||
<header class="text-white bg-gray-900 border-b">
|
||||
<div class="flex items-center justify-between px-4 py-4 mx-auto">
|
||||
<a href="<?= route_to('home') ?>" class="text-xl">Castopod Admin</a>
|
||||
<nav>
|
||||
<div class="flex items-center px-4 py-4 mx-auto">
|
||||
<a href="<?= route_to('admin') ?>" class="text-xl">Castopod Admin</a>
|
||||
<a href="<?= route_to(
|
||||
'home'
|
||||
) ?>" class="ml-4 text-sm underline hover:no-underline">Go to website</a>
|
||||
<nav class="ml-auto">
|
||||
<span class="mr-2">Welcome, <?= user()->username ?></span>
|
||||
<a class="px-4 py-2 border hover:bg-gray-800" href="<?= route_to(
|
||||
'logout'
|
||||
|
@ -25,9 +28,8 @@
|
|||
<div class="flex flex-1">
|
||||
<?= view('admin/_sidenav') ?>
|
||||
<main class="container flex-1 px-4 py-6 mx-auto">
|
||||
<div class="mb-4">
|
||||
<?= view('_message_block') ?>
|
||||
</div>
|
||||
<h1 class="mb-4 text-2xl"><?= $this->renderSection('title') ?></h1>
|
||||
<?= view('_message_block') ?>
|
||||
<?= $this->renderSection('content') ?>
|
||||
</main>
|
||||
</div>
|
||||
|
|
|
@ -8,6 +8,11 @@
|
|||
<div class="mb-4">
|
||||
<span class="mb-3 text-sm font-bold tracking-wide text-gray-600 uppercase lg:mb-2 lg:text-xs">Podcasts</span>
|
||||
<ul>
|
||||
<li>
|
||||
<a class="block px-2 py-1 -mx-2 text-gray-600 transition duration-200 ease-in-out hover:text-gray-900" href="<?= route_to(
|
||||
'my_podcasts'
|
||||
) ?>">My podcasts</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="block px-2 py-1 -mx-2 text-gray-600 transition duration-200 ease-in-out hover:text-gray-900" href="<?= route_to(
|
||||
'podcast_list'
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
<?= $this->extend('admin/_layout') ?>
|
||||
|
||||
<?= $this->section('title') ?>
|
||||
<?= lang('Contributor.add_contributor', [$podcast->title]) ?>
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<form action="<?= route_to(
|
||||
'contributor_add',
|
||||
$podcast->id
|
||||
) ?>" method="post" class="flex flex-col max-w-lg">
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<div class="flex flex-col mb-4">
|
||||
<label for="user"><?= lang('Contributor.form.user') ?></label>
|
||||
<select id="user" name="user" autocomplete="off" class="form-select" required>
|
||||
<?php foreach ($users as $user): ?>
|
||||
<option value="<?= $user->id ?>"
|
||||
<?php if (
|
||||
old('user') == $user->id
|
||||
): ?> selected <?php endif; ?>>
|
||||
<?= $user->username ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col mb-4">
|
||||
<label for="role"><?= lang('Contributor.form.role') ?></label>
|
||||
<select id="role" name="role" autocomplete="off" class="form-select" required>
|
||||
<?php foreach ($roles as $role): ?>
|
||||
<option value="<?= $role->id ?>"
|
||||
<?php if (
|
||||
old('role') == $role->id
|
||||
): ?> selected <?php endif; ?>>
|
||||
<?= $role->name ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button type="submit" name="submit" class="self-end px-4 py-2 bg-gray-200"><?= lang(
|
||||
'Contributor.form.submit_add'
|
||||
) ?></button>
|
||||
</form>
|
||||
|
||||
<?= $this->endSection() ?>
|
|
@ -0,0 +1,35 @@
|
|||
<?= $this->extend('admin/_layout') ?>
|
||||
|
||||
<?= $this->section('title') ?>
|
||||
<?= lang('Contributor.edit_role', [$user->username]) ?>
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<form action="<?= route_to(
|
||||
'contributor_edit',
|
||||
$podcast->id,
|
||||
$user->id
|
||||
) ?>" method="post" class="flex flex-col max-w-lg">
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<div class="flex flex-col mb-4">
|
||||
<label for="category"><?= lang('Contributor.form.role') ?></label>
|
||||
<select id="role" name="role" autocomplete="off" class="form-select" required>
|
||||
<?php foreach ($roles as $role): ?>
|
||||
<option value="<?= $role->id ?>"
|
||||
<?php if (
|
||||
old('role') == $role->id
|
||||
): ?> selected <?php endif; ?>>
|
||||
<?= $role->name ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button type="submit" name="submit" class="self-end px-4 py-2 bg-gray-200"><?= lang(
|
||||
'Contributor.form.submit_edit'
|
||||
) ?></button>
|
||||
|
||||
</form>
|
||||
<?= $this->endSection() ?>
|
|
@ -0,0 +1,47 @@
|
|||
<?= $this->extend('admin/_layout') ?>
|
||||
|
||||
<?= $this->section('title') ?>
|
||||
<?= lang('Contributor.podcast_contributors') ?>
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<a class="inline-block px-4 py-2 mb-2 border hover:bg-gray-100" href="<?= route_to(
|
||||
'contributor_add',
|
||||
$podcast->id
|
||||
) ?>"><?= lang('Contributor.add') ?></a>
|
||||
|
||||
<table class="table-auto">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-4 py-2">Username</th>
|
||||
<th class="px-4 py-2">Permissions</th>
|
||||
<th class="px-4 py-2">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($podcast->contributors as $contributor): ?>
|
||||
<tr>
|
||||
<td class="px-4 py-2 border"><?= $contributor->username ?></td>
|
||||
<td class="px-4 py-2 border">[<?= implode(
|
||||
', ',
|
||||
$contributor->permissions
|
||||
) ?>]</td>
|
||||
<td class="px-4 py-2 border">
|
||||
<a class="inline-flex px-2 py-1 mb-2 text-sm text-white bg-teal-700 hover:bg-teal-800" href="<?= route_to(
|
||||
'contributor_edit',
|
||||
$podcast->id,
|
||||
$contributor->id
|
||||
) ?>"><?= lang('Contributor.edit') ?></a>
|
||||
<a class="inline-flex px-2 py-1 text-sm text-white bg-red-700 hover:bg-red-800" href="<?= route_to(
|
||||
'contributor_remove',
|
||||
$podcast->id,
|
||||
$contributor->id
|
||||
) ?>"><?= lang('Contributor.remove') ?></a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<?= $this->endSection() ?>
|
|
@ -1,8 +1,6 @@
|
|||
<?= $this->extend('admin/_layout') ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
|
||||
<h1 class="text-2xl">Welcome to the admin dashboard!</h1>
|
||||
|
||||
<?= $this->endSection() ?>
|
||||
<?= $this->section(
|
||||
'title'
|
||||
) ?>Welcome to the admin dashboard!<?= $this->endSection() ?>
|
||||
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
<?= $this->extend('admin/_layout') ?>
|
||||
|
||||
<?= $this->section('title') ?>
|
||||
<?= lang('Episode.create') ?>
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
|
||||
<h1 class="mb-6 text-xl"><?= lang('Episode.create') ?></h1>
|
||||
|
||||
<div class="mb-8">
|
||||
<?= \Config\Services::validation()->listErrors() ?>
|
||||
</div>
|
||||
|
||||
<?= form_open_multipart(route_to('episode_create', $podcast->name), [
|
||||
<?= form_open_multipart(route_to('episode_create', $podcast->id), [
|
||||
'method' => 'post',
|
||||
'class' => 'flex flex-col max-w-md',
|
||||
]) ?>
|
||||
|
@ -21,24 +20,30 @@
|
|||
|
||||
<div class="flex flex-col mb-4">
|
||||
<label for="title"><?= lang('Episode.form.title') ?></label>
|
||||
<input type="text" class="form-input" id="title" name="title" required />
|
||||
<input type="text" class="form-input" id="title" name="title" required value="<?= old(
|
||||
'title'
|
||||
) ?>" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col mb-4">
|
||||
<label for="slug"><?= lang('Episode.form.slug') ?></label>
|
||||
<input type="text" class="form-input" id="slug" name="slug" required />
|
||||
<input type="text" class="form-input" id="slug" name="slug" required value="<?= old(
|
||||
'slug'
|
||||
) ?>" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col mb-4">
|
||||
<label for="description"><?= lang('Episode.form.description') ?></label>
|
||||
<textarea class="form-textarea" id="description" name="description" required></textarea>
|
||||
<textarea class="form-textarea" id="description" name="description" required><?= old(
|
||||
'description'
|
||||
) ?></textarea>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col mb-4">
|
||||
<label for="pub_date"><?= lang('Episode.form.pub_date') ?></label>
|
||||
<input type="date" class="form-input" id="pub_date" name="pub_date" value="<?= date(
|
||||
'Y-m-d'
|
||||
) ?>" />
|
||||
<input type="date" class="form-input" id="pub_date" name="pub_date" value="<?= old(
|
||||
'pub_date'
|
||||
) || date('Y-m-d') ?>" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col mb-4">
|
||||
|
@ -50,16 +55,22 @@
|
|||
<label for="episode_number"><?= lang(
|
||||
'Episode.form.episode_number'
|
||||
) ?></label>
|
||||
<input type="number" class="form-input" id="episode_number" name="episode_number" required />
|
||||
<input type="number" class="form-input" id="episode_number" name="episode_number" required value="<?= old(
|
||||
'episode_number'
|
||||
) ?>" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col mb-4">
|
||||
<label for="season_number"><?= lang('Episode.form.season_number') ?></label>
|
||||
<input type="number" class="form-input" id="season_number" name="season_number" />
|
||||
<input type="number" class="form-input" id="season_number" name="season_number" value="<?= old(
|
||||
'season_number'
|
||||
) ?>" />
|
||||
</div>
|
||||
|
||||
<div class="inline-flex items-center mb-4">
|
||||
<input type="checkbox" id="explicit" name="explicit" class="form-checkbox" />
|
||||
<input type="checkbox" id="explicit" name="explicit" class="form-checkbox" <?php if (
|
||||
old('explicit')
|
||||
): ?> checked <?php endif; ?> />
|
||||
<label for="explicit" class="pl-2"><?= lang(
|
||||
'Episode.form.explicit'
|
||||
) ?></label>
|
||||
|
@ -67,32 +78,45 @@
|
|||
|
||||
<div class="flex flex-col mb-4">
|
||||
<label for="author_name"><?= lang('Podcast.form.author_name') ?></label>
|
||||
<input type="text" class="form-input" id="author_name" name="author_name" />
|
||||
<input type="text" class="form-input" id="author_name" name="author_name" value="<?= old(
|
||||
'author_name'
|
||||
) ?>" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col mb-4">
|
||||
<label for="author_email"><?= lang('Podcast.form.author_email') ?></label>
|
||||
<input type="email" class="form-input" id="author_email" name="author_email" />
|
||||
<input type="email" class="form-input" id="author_email" name="author_email" value="<?= old(
|
||||
'author_email'
|
||||
) ?>" />
|
||||
</div>
|
||||
|
||||
<fieldset class="flex flex-col mb-4">
|
||||
<legend><?= lang('Episode.form.type.label') ?></legend>
|
||||
<label for="full" class="inline-flex items-center">
|
||||
<input type="radio" class="form-radio" value="full" id="full" name="type" required checked />
|
||||
<input type="radio" class="form-radio" value="full" id="full" name="type" required <?php if (
|
||||
!old('type') ||
|
||||
old('type') == 'full'
|
||||
): ?> checked <?php endif; ?> />
|
||||
<span class="ml-2"><?= lang('Episode.form.type.full') ?></span>
|
||||
</label>
|
||||
<label for="trailer" class="inline-flex items-center">
|
||||
<input type="radio" class="form-radio" value="trailer" id="trailer" name="type" required />
|
||||
<input type="radio" class="form-radio" value="trailer" id="trailer" name="type" required <?php if (
|
||||
old('type') == 'trailer'
|
||||
): ?> checked <?php endif; ?> />
|
||||
<span class="ml-2"><?= lang('Episode.form.type.trailer') ?></span>
|
||||
</label>
|
||||
<label for="bonus" class="inline-flex items-center">
|
||||
<input type="radio" class="form-radio" value="bonus" id="bonus" name="type" required />
|
||||
<input type="radio" class="form-radio" value="bonus" id="bonus" name="type" required <?php if (
|
||||
old('type') == 'bonus'
|
||||
): ?> checked <?php endif; ?> />
|
||||
<span class="ml-2"><?= lang('Episode.form.type.bonus') ?></span>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<div class="inline-flex items-center mb-4">
|
||||
<input type="checkbox" id="block" name="block" class="form-checkbox" />
|
||||
<input type="checkbox" id="block" name="block" class="form-checkbox" <?php if (
|
||||
old('block')
|
||||
): ?> checked <?php endif; ?> />
|
||||
<label for="block" class="pl-2"><?= lang('Episode.form.block') ?></label>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,20 +1,16 @@
|
|||
<?= $this->extend('admin/_layout') ?>
|
||||
|
||||
<?= $this->section('title') ?>
|
||||
<?= lang('Episode.edit') ?>
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
|
||||
<h1 class="mb-6 text-xl"><?= lang('Episode.edit') ?></h1>
|
||||
|
||||
<div class="mb-8">
|
||||
<?= \Config\Services::validation()->listErrors() ?>
|
||||
</div>
|
||||
|
||||
<?= form_open_multipart(
|
||||
route_to('episode_edit', $podcast->name, $episode->slug),
|
||||
[
|
||||
'method' => 'post',
|
||||
'class' => 'flex flex-col max-w-md',
|
||||
]
|
||||
) ?>
|
||||
<?= form_open_multipart(route_to('episode_edit', $podcast->id, $episode->id), [
|
||||
'method' => 'post',
|
||||
'class' => 'flex flex-col max-w-md',
|
||||
]) ?>
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<div class="flex flex-col mb-4">
|
||||
|
|
|
@ -1,21 +1,29 @@
|
|||
<?= $this->extend('admin/_layout') ?>
|
||||
|
||||
<?= $this->section('title') ?>
|
||||
|
||||
<?= lang('Episode.all_podcast_episodes') ?> (<?= count($podcast->episodes) ?>)
|
||||
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
|
||||
<a class="inline-block px-4 py-2 mb-2 border hover:bg-gray-100" href="<?= route_to(
|
||||
'episode_create',
|
||||
$podcast->id
|
||||
) ?>"><?= lang('Episode.create') ?></a>
|
||||
<div class="flex flex-col py-4">
|
||||
<h1 class="mb-4 text-xl"><?= lang(
|
||||
'Episode.all_podcast_episodes'
|
||||
) ?> (<?= count($all_podcast_episodes) ?>)</h1>
|
||||
<?php if ($all_podcast_episodes): ?>
|
||||
<?php foreach ($all_podcast_episodes as $episode): ?>
|
||||
<?php if ($podcast->episodes): ?>
|
||||
<?php foreach ($podcast->episodes as $episode): ?>
|
||||
<article class="flex-col w-full max-w-lg p-4 mb-4 border shadow">
|
||||
<div class="flex mb-2">
|
||||
<img src="<?= $episode->image_url ?>" alt="<?= $episode->title ?>" class="object-cover w-32 h-32 mr-4" />
|
||||
<div class="flex flex-col flex-1">
|
||||
<a href="<?= route_to(
|
||||
'episode_edit',
|
||||
$podcast->name,
|
||||
$episode->slug
|
||||
$podcast->id,
|
||||
$episode->id
|
||||
) ?>">
|
||||
<h3 class="text-xl font-semibold">
|
||||
<span class="mr-1 underline hover:no-underline"><?= $episode->title ?></span>
|
||||
|
@ -24,15 +32,15 @@
|
|||
<p><?= $episode->description ?></p>
|
||||
</a>
|
||||
<audio controls class="mt-auto" preload="none">
|
||||
<source src="<?= $episode->enclosure_url ?>" type="<?= $episode->enclosure_type ?>">
|
||||
<source src="<?= $episode->enclosure_media_path ?>" type="<?= $episode->enclosure_type ?>">
|
||||
Your browser does not support the audio tag.
|
||||
</audio>
|
||||
</div>
|
||||
</div>
|
||||
<a class="inline-flex px-4 py-2 text-white bg-teal-700 hover:bg-teal-800" href="<?= route_to(
|
||||
'episode_edit',
|
||||
$podcast->name,
|
||||
$episode->slug
|
||||
$podcast->id,
|
||||
$episode->id
|
||||
) ?>"><?= lang('Episode.edit') ?></a>
|
||||
<a href="<?= route_to(
|
||||
'episode',
|
||||
|
@ -43,21 +51,15 @@
|
|||
) ?></a>
|
||||
<a href="<?= route_to(
|
||||
'episode_delete',
|
||||
$podcast->name,
|
||||
$episode->slug
|
||||
$podcast->id,
|
||||
$episode->id
|
||||
) ?>" class="inline-flex px-4 py-2 text-white bg-red-700 hover:bg-red-800"><?= lang(
|
||||
'Episode.delete'
|
||||
) ?></a>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<div class="flex items-center">
|
||||
<p class="mr-4 italic"><?= lang('Podcast.no_episode') ?></p>
|
||||
<a class="self-start px-4 py-2 border hover:bg-gray-100" href="<?= route_to(
|
||||
'episode_create',
|
||||
$podcast->name
|
||||
) ?>"><?= lang('Episode.create_one') ?></a>
|
||||
</div>
|
||||
<p class="italic"><?= lang('Podcast.no_episode') ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
<?= $this->extend('admin/_layout') ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<?= $this->section('title') ?>
|
||||
<?= lang('MyAccount.changePassword') ?>
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
<h1 class="mb-6 text-xl"><?= lang('MyAccount.changePassword') ?></h1>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
|
||||
<form action="<?= route_to(
|
||||
'myAccount_changePassword'
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
<?= $this->extend('admin/_layout') ?>
|
||||
|
||||
<?= $this->section('title') ?>
|
||||
<?= lang('MyAccount.info') ?>
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
|
||||
<div class="px-4 py-5 bg-gray-50 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
<?= $this->extend('admin/_layout') ?>
|
||||
|
||||
<?= $this->section('title') ?>
|
||||
<?= lang('Podcast.create') ?>
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
|
||||
<h1 class="mb-6 text-xl"><?= lang('Podcast.create') ?></h1>
|
||||
|
||||
<div class="mb-8">
|
||||
<?= \Config\Services::validation()->listErrors() ?>
|
||||
</div>
|
||||
|
||||
<?= form_open_multipart(base_url(route_to('podcast_create')), [
|
||||
<?= form_open_multipart(route_to('podcast_create'), [
|
||||
'method' => 'post',
|
||||
'class' => 'flex flex-col max-w-md',
|
||||
]) ?>
|
||||
|
@ -16,24 +15,32 @@
|
|||
|
||||
<div class="flex flex-col mb-4">
|
||||
<label for="title"><?= lang('Podcast.form.title') ?></label>
|
||||
<input type="text" class="form-input" id="title" name="title" required />
|
||||
<input type="text" class="form-input" id="title" name="title" value="<?= old(
|
||||
'title'
|
||||
) ?>" required />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col mb-4">
|
||||
<label for="name"><?= lang('Podcast.form.name') ?></label>
|
||||
<input type="text" class="form-input" id="name" name="name" required />
|
||||
<input type="text" class="form-input" id="name" name="name" value="<?= old(
|
||||
'name'
|
||||
) ?>" required />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col mb-4">
|
||||
<label for="description"><?= lang('Podcast.form.description') ?></label>
|
||||
<textarea class="form-textarea" id="description" name="description" required></textarea>
|
||||
<textarea class="form-textarea" id="description" name="description" required><?= old(
|
||||
'description'
|
||||
) ?></textarea>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col mb-4">
|
||||
<label for="episode_description_footer"><?= lang(
|
||||
'Podcast.form.episode_description_footer'
|
||||
) ?></label>
|
||||
<textarea class="form-textarea" id="episode_description_footer" name="episode_description_footer"></textarea>
|
||||
<textarea class="form-textarea" id="episode_description_footer" name="episode_description_footer"><?= old(
|
||||
'episode_description_footer'
|
||||
) ?></textarea>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col mb-4">
|
||||
|
@ -45,9 +52,15 @@
|
|||
<label for="language"><?= lang('Podcast.form.language') ?></label>
|
||||
<select id="language" name="language" autocomplete="off" class="form-select" required>
|
||||
<?php foreach ($languages as $language): ?>
|
||||
<option <?= $language->code == $browser_lang
|
||||
? "selected='selected'"
|
||||
: '' ?> value="<?= $language->code ?>">
|
||||
<option value="<?= $language->code ?>"
|
||||
<?php if (
|
||||
old('language') == $language->code
|
||||
): ?> selected <?php endif; ?>
|
||||
<?php if (
|
||||
!old('language') &&
|
||||
$language->code == $browser_lang
|
||||
): ?> selected <?php endif; ?>
|
||||
>
|
||||
<?= $language->native_name ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
|
@ -58,16 +71,20 @@
|
|||
<label for="category"><?= lang('Podcast.form.category') ?></label>
|
||||
<select id="category" name="category" class="form-select" required>
|
||||
<?php foreach ($categories as $category): ?>
|
||||
<option value="<?= $category->code ?>"><?= lang(
|
||||
'Podcast.category_options.' . $category->code
|
||||
) ?>
|
||||
<option value="<?= $category->code ?>"
|
||||
<?php if (
|
||||
old('category') == $category->code
|
||||
): ?> selected <?php endif; ?>
|
||||
><?= lang('Podcast.category_options.' . $category->code) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="inline-flex items-center mb-4">
|
||||
<input type="checkbox" id="explicit" name="explicit" class="form-checkbox" />
|
||||
<input type="checkbox" id="explicit" name="explicit" class="form-checkbox" <?php if (
|
||||
old('explicit')
|
||||
): ?> checked <?php endif; ?> />
|
||||
<label for="explicit" class="pl-2"><?= lang(
|
||||
'Podcast.form.explicit'
|
||||
) ?></label>
|
||||
|
@ -75,48 +92,67 @@
|
|||
|
||||
<div class="flex flex-col mb-4">
|
||||
<label for="author_name"><?= lang('Podcast.form.author_name') ?></label>
|
||||
<input type="text" class="form-input" id="author_name" name="author_name" />
|
||||
<input type="text" class="form-input" id="author_name" name="author_name" value="<?= old(
|
||||
'author_name'
|
||||
) ?>" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col mb-4">
|
||||
<label for="author_email"><?= lang('Podcast.form.author_email') ?></label>
|
||||
<input type="email" class="form-input" id="author_email" name="author_email" />
|
||||
<input type="email" class="form-input" id="author_email" name="author_email" value="<?= old(
|
||||
'author_email'
|
||||
) ?>" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col mb-4">
|
||||
<label for="owner_name"><?= lang('Podcast.form.owner_name') ?></label>
|
||||
<input type="text" class="form-input" id="owner_name" name="owner_name" />
|
||||
<input type="text" class="form-input" id="owner_name" name="owner_name" value="<?= old(
|
||||
'owner_name'
|
||||
) ?>" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col mb-4">
|
||||
<label for="owner_email"><?= lang('Podcast.form.owner_email') ?></label>
|
||||
<input type="email" class="form-input" id="owner_email" name="owner_email" required />
|
||||
<input type="email" class="form-input" id="owner_email" name="owner_email" value="<?= old(
|
||||
'owner_email'
|
||||
) ?>" required />
|
||||
</div>
|
||||
|
||||
<fieldset class="flex flex-col mb-4">
|
||||
<legend><?= lang('Podcast.form.type.label') ?></legend>
|
||||
<label for="episodic" class="inline-flex items-center">
|
||||
<input type="radio" class="form-radio" value="episodic" id="episodic" name="type" required checked />
|
||||
<span class="ml-2"><?= lang('Podcast.form.type.episodic') ?></span>
|
||||
<input type="radio" class="form-radio" value="episodic" id="episodic" name="type" required <?php if (
|
||||
!old('type') ||
|
||||
old('type') == 'episodic'
|
||||
): ?> checked <?php endif; ?> />
|
||||
<span class="ml-2"><?= lang('Podcast.form.type.episodic') ?></span>
|
||||
</label>
|
||||
<label for="serial" class="inline-flex items-center">
|
||||
<input type="radio" class="form-radio" value="serial" id="serial" name="type" required />
|
||||
<span class="ml-2"><?= lang('Podcast.form.type.serial') ?></span>
|
||||
<input type="radio" class="form-radio" value="serial" id="serial" name="type" required <?php if (
|
||||
old('type') == 'serial'
|
||||
): ?> checked <?php endif; ?> />
|
||||
<span class="ml-2"><?= lang('Podcast.form.type.serial') ?></span>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<div class="flex flex-col mb-4">
|
||||
<label for="copyright"><?= lang('Podcast.form.copyright') ?></label>
|
||||
<input type="text" class="form-input" id="copyright" name="copyright" />
|
||||
<input type="text" class="form-input" id="copyright" name="copyright" value="<?= old(
|
||||
'copyright'
|
||||
) ?>" />
|
||||
</div>
|
||||
|
||||
<div class="inline-flex items-center mb-4">
|
||||
<input type="checkbox" id="block" name="block" class="form-checkbox" />
|
||||
<input type="checkbox" id="block" name="block" class="form-checkbox" <?php if (
|
||||
old('block')
|
||||
): ?> checked <?php endif; ?> />
|
||||
<label for="block" class="pl-2"><?= lang('Podcast.form.block') ?></label>
|
||||
</div>
|
||||
|
||||
<div class="inline-flex items-center mb-4">
|
||||
<input type="checkbox" id="complete" name="complete" class="form-checkbox" />
|
||||
<input type="checkbox" id="complete" name="complete" class="form-checkbox" <?php if (
|
||||
old('complete')
|
||||
): ?> checked <?php endif; ?> />
|
||||
<label for="complete" class="pl-2"><?= lang(
|
||||
'Podcast.form.complete'
|
||||
) ?></label>
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
<?= $this->extend('admin/_layout') ?>
|
||||
|
||||
<?= $this->section('title') ?>
|
||||
<?= lang('Podcast.edit') ?>
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
|
||||
<h1 class="mb-6 text-xl"><?= lang('Podcast.edit') ?></h1>
|
||||
|
||||
<div class="mb-8">
|
||||
<?= \Config\Services::validation()->listErrors() ?>
|
||||
</div>
|
||||
|
||||
<?= form_open_multipart(base_url(route_to('podcast_edit', $podcast->name)), [
|
||||
<?= form_open_multipart(route_to('podcast_edit', $podcast->id), [
|
||||
'method' => 'post',
|
||||
'class' => 'flex flex-col max-w-md',
|
||||
]) ?>
|
||||
|
@ -70,7 +69,9 @@
|
|||
</div>
|
||||
|
||||
<div class="inline-flex items-center mb-4">
|
||||
<input type="checkbox" id="explicit" name="explicit" class="form-checkbox" checked="<?= $podcast->explicit ?>" />
|
||||
<input type="checkbox" id="explicit" name="explicit" class="form-checkbox" <?= $podcast->explicit
|
||||
? 'checked'
|
||||
: '' ?> />
|
||||
<label for="explicit" class="pl-2"><?= lang(
|
||||
'Podcast.form.explicit'
|
||||
) ?></label>
|
||||
|
@ -123,7 +124,7 @@
|
|||
|
||||
<div class="inline-flex items-center mb-4">
|
||||
<input type="checkbox" id="complete" name="complete" class="form-checkbox"
|
||||
<?= $podcast->block ? 'checked' : '' ?> />
|
||||
<?= $podcast->complete ? 'checked' : '' ?> />
|
||||
<label for="complete" class="pl-2"><?= lang(
|
||||
'Podcast.form.complete'
|
||||
) ?></label>
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
<?= $this->extend('admin/_layout') ?>
|
||||
|
||||
<?= $this->section('title') ?>
|
||||
<?= lang('Podcast.all_podcasts') ?> (<?= count($all_podcasts) ?>)
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
|
||||
<h1 class="mb-2 text-xl"><?= lang('Podcast.all_podcasts') ?> (<?= count(
|
||||
$all_podcasts
|
||||
) ?>)</h1>
|
||||
<a class="inline-block px-4 py-2 mb-2 border hover:bg-gray-100" href="<?= route_to(
|
||||
'podcast_create'
|
||||
) ?>"><?= lang('Podcast.create') ?></a>
|
||||
<div class="flex flex-wrap">
|
||||
<?php if ($all_podcasts): ?>
|
||||
<?php foreach ($all_podcasts as $podcast): ?>
|
||||
|
@ -12,36 +17,36 @@
|
|||
<img alt="<?= $podcast->title ?>" src="<?= $podcast->image_url ?>" class="object-cover w-full h-40 mb-2" />
|
||||
<a href="<?= route_to(
|
||||
'episode_list',
|
||||
$podcast->name
|
||||
$podcast->id
|
||||
) ?>" class="hover:underline">
|
||||
<h2 class="font-semibold leading-tight"><?= $podcast->title ?></h2>
|
||||
</a>
|
||||
<p class="mb-4 text-gray-600">@<?= $podcast->name ?></p>
|
||||
<a class="inline-flex px-2 py-1 mb-2 text-white bg-teal-700 hover:bg-teal-800" href="<?= route_to(
|
||||
'podcast_edit',
|
||||
$podcast->name
|
||||
$podcast->id
|
||||
) ?>"><?= lang('Podcast.edit') ?></a>
|
||||
<a class="inline-flex px-2 py-1 mb-2 text-white bg-indigo-700 hover:bg-indigo-800" href="<?= route_to(
|
||||
'episode_list',
|
||||
$podcast->name
|
||||
$podcast->id
|
||||
) ?>"><?= lang('Podcast.see_episodes') ?></a>
|
||||
<a class="inline-flex px-2 py-1 mb-2 text-white bg-yellow-700 hover:bg-yellow-800" href="<?= route_to(
|
||||
'contributor_list',
|
||||
$podcast->id
|
||||
) ?>"><?= lang('Podcast.see_contributors') ?></a>
|
||||
<a class="inline-flex px-2 py-1 text-white bg-gray-700 hover:bg-gray-800" href="<?= route_to(
|
||||
'podcast',
|
||||
$podcast->name
|
||||
) ?>"><?= lang('Podcast.goto_page') ?></a>
|
||||
<a class="inline-flex px-2 py-1 text-white bg-red-700 hover:bg-red-800" href="<?= route_to(
|
||||
'podcast_delete',
|
||||
$podcast->name
|
||||
$podcast->id
|
||||
) ?>"><?= lang('Podcast.delete') ?></a>
|
||||
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<div class="flex items-center">
|
||||
<p class="mr-4 italic"><?= lang('Podcast.no_podcast') ?></p>
|
||||
<a class="self-start px-4 py-2 border hover:bg-gray-100 " href="<?= route_to(
|
||||
'podcast_create'
|
||||
) ?>"><?= lang('Podcast.create_one') ?></a>
|
||||
</div>
|
||||
<p class="italic"><?= lang('Podcast.no_podcast') ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
<?= $this->extend('admin/_layout') ?>
|
||||
|
||||
<?= $this->section('title') ?>
|
||||
<?= lang('User.create') ?>
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
|
||||
<h1 class="mb-6 text-xl"><?= lang('User.create') ?></h1>
|
||||
|
||||
<div class="mb-8">
|
||||
<?= \Config\Services::validation()->listErrors() ?>
|
||||
</div>
|
||||
|
||||
<form action="<?= route_to(
|
||||
'user_create'
|
||||
) ?>" method="post" class="flex flex-col max-w-lg">
|
||||
|
|
|
@ -1,61 +1,55 @@
|
|||
<?= $this->extend('admin/_layout') ?>
|
||||
|
||||
<?= $this->section('title') ?>
|
||||
<?= lang('User.all_users') ?> (<?= count($all_users) ?>)
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
|
||||
<div class="flex flex-wrap">
|
||||
<?php if ($all_users): ?>
|
||||
<table class="table-auto">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-4 py-2">Username</th>
|
||||
<th class="px-4 py-2">Email</th>
|
||||
<th class="px-4 py-2">Permissions</th>
|
||||
<th class="px-4 py-2">Banned?</th>
|
||||
<th class="px-4 py-2">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($all_users as $user): ?>
|
||||
<tr>
|
||||
<td class="px-4 py-2 border"><?= $user->username ?></td>
|
||||
<td class="px-4 py-2 border"><?= $user->email ?></td>
|
||||
<td class="px-4 py-2 border">[<?= implode(
|
||||
', ',
|
||||
$user->permissions
|
||||
) ?>]</td>
|
||||
<td class="px-4 py-2 border"><?= $user->isBanned()
|
||||
? 'Yes'
|
||||
: 'No' ?></td>
|
||||
<td class="px-4 py-2 border">
|
||||
<a class="inline-flex px-2 py-1 mb-2 text-sm text-white bg-teal-700 hover:bg-teal-800" href="<?= route_to(
|
||||
'user_force_pass_reset',
|
||||
$user->username
|
||||
) ?>"><?= lang('User.forcePassReset') ?></a>
|
||||
<a class="inline-flex px-2 py-1 mb-2 text-sm text-white bg-orange-700 hover:bg-orange-800" href="<?= route_to(
|
||||
$user->isBanned() ? 'user_unban' : 'user_ban',
|
||||
$user->username
|
||||
) ?>">
|
||||
<?= $user->isBanned()
|
||||
? lang('User.unban')
|
||||
: lang('User.ban') ?></a>
|
||||
<a class="inline-flex px-2 py-1 text-sm text-white bg-red-700 hover:bg-red-800" href="<?= route_to(
|
||||
'user_delete',
|
||||
$user->username
|
||||
) ?>"><?= lang('User.delete') ?></a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php else: ?>
|
||||
<div class="flex items-center">
|
||||
<p class="mr-4 italic"><?= lang('Podcast.no_podcast') ?></p>
|
||||
<a class="self-start px-4 py-2 border hover:bg-gray-100 " href="<?= route_to(
|
||||
'podcast_create'
|
||||
) ?>"><?= lang('Podcast.create_one') ?></a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<table class="table-auto">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-4 py-2">Username</th>
|
||||
<th class="px-4 py-2">Email</th>
|
||||
<th class="px-4 py-2">Permissions</th>
|
||||
<th class="px-4 py-2">Banned?</th>
|
||||
<th class="px-4 py-2">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($all_users as $user): ?>
|
||||
<tr>
|
||||
<td class="px-4 py-2 border"><?= $user->username ?></td>
|
||||
<td class="px-4 py-2 border"><?= $user->email ?></td>
|
||||
<td class="px-4 py-2 border">[<?= implode(
|
||||
', ',
|
||||
$user->permissions
|
||||
) ?>]</td>
|
||||
<td class="px-4 py-2 border"><?= $user->isBanned()
|
||||
? 'Yes'
|
||||
: 'No' ?></td>
|
||||
<td class="px-4 py-2 border">
|
||||
<a class="inline-flex px-2 py-1 mb-2 text-sm text-white bg-teal-700 hover:bg-teal-800" href="<?= route_to(
|
||||
'user_force_pass_reset',
|
||||
$user->id
|
||||
) ?>"><?= lang('User.forcePassReset') ?></a>
|
||||
<a class="inline-flex px-2 py-1 mb-2 text-sm text-white bg-orange-700 hover:bg-orange-800" href="<?= route_to(
|
||||
$user->isBanned() ? 'user_unban' : 'user_ban',
|
||||
$user->id
|
||||
) ?>">
|
||||
<?= $user->isBanned()
|
||||
? lang('User.unban')
|
||||
: lang('User.ban') ?></a>
|
||||
<a class="inline-flex px-2 py-1 text-sm text-white bg-red-700 hover:bg-red-800" href="<?= route_to(
|
||||
'user_delete',
|
||||
$user->id
|
||||
) ?>"><?= lang('User.delete') ?></a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<?= $this->endSection()
|
||||
?>
|
||||
|
|
|
@ -17,9 +17,7 @@
|
|||
) ?></a>
|
||||
</header>
|
||||
<main class="w-full max-w-md px-6 py-4 mx-auto bg-white rounded-lg shadow">
|
||||
<div class="mb-4">
|
||||
<?= view('_message_block') ?>
|
||||
</div>
|
||||
<?= view('_message_block') ?>
|
||||
<?= $this->renderSection('content') ?>
|
||||
</main>
|
||||
<footer class="flex flex-col text-sm">
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
"license": "AGPL-3.0-or-later",
|
||||
"require": {
|
||||
"php": ">=7.2",
|
||||
"codeigniter4/framework": "^4",
|
||||
"codeigniter4/framework": "4.0.3",
|
||||
"james-heinrich/getid3": "~2.0.0-dev",
|
||||
"whichbrowser/parser": "^2.0",
|
||||
"geoip2/geoip2": "~2.0",
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "a03d5be6665057254fa301cada96586e",
|
||||
"content-hash": "e494a281a4c6a239790ea930d05764e2",
|
||||
"packages": [
|
||||
{
|
||||
"name": "codeigniter4/framework",
|
||||
|
@ -1158,33 +1158,33 @@
|
|||
},
|
||||
{
|
||||
"name": "phpspec/prophecy",
|
||||
"version": "v1.10.3",
|
||||
"version": "1.11.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpspec/prophecy.git",
|
||||
"reference": "451c3cd1418cf640de218914901e51b064abb093"
|
||||
"reference": "b20034be5efcdab4fb60ca3a29cba2949aead160"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093",
|
||||
"reference": "451c3cd1418cf640de218914901e51b064abb093",
|
||||
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/b20034be5efcdab4fb60ca3a29cba2949aead160",
|
||||
"reference": "b20034be5efcdab4fb60ca3a29cba2949aead160",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"doctrine/instantiator": "^1.0.2",
|
||||
"php": "^5.3|^7.0",
|
||||
"phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0",
|
||||
"sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0",
|
||||
"sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0"
|
||||
"doctrine/instantiator": "^1.2",
|
||||
"php": "^7.2",
|
||||
"phpdocumentor/reflection-docblock": "^5.0",
|
||||
"sebastian/comparator": "^3.0 || ^4.0",
|
||||
"sebastian/recursion-context": "^3.0 || ^4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpspec/phpspec": "^2.5 || ^3.2",
|
||||
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1"
|
||||
"phpspec/phpspec": "^6.0",
|
||||
"phpunit/phpunit": "^8.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.10.x-dev"
|
||||
"dev-master": "1.11.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
|
@ -1217,7 +1217,7 @@
|
|||
"spy",
|
||||
"stub"
|
||||
],
|
||||
"time": "2020-03-05T15:02:03+00:00"
|
||||
"time": "2020-07-08T12:44:21+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
|
@ -2181,16 +2181,16 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
"version": "v1.17.1",
|
||||
"version": "v1.18.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||
"reference": "2edd75b8b35d62fd3eeabba73b26b8f1f60ce13d"
|
||||
"reference": "1c302646f6efc070cd46856e600e5e0684d6b454"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/2edd75b8b35d62fd3eeabba73b26b8f1f60ce13d",
|
||||
"reference": "2edd75b8b35d62fd3eeabba73b26b8f1f60ce13d",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/1c302646f6efc070cd46856e600e5e0684d6b454",
|
||||
"reference": "1c302646f6efc070cd46856e600e5e0684d6b454",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -2202,7 +2202,7 @@
|
|||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.17-dev"
|
||||
"dev-master": "1.18-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
|
@ -2253,27 +2253,27 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-06-06T08:46:27+00:00"
|
||||
"time": "2020-07-14T12:35:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "theseer/tokenizer",
|
||||
"version": "1.1.3",
|
||||
"version": "1.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/theseer/tokenizer.git",
|
||||
"reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9"
|
||||
"reference": "75a63c33a8577608444246075ea0af0d052e452a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9",
|
||||
"reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9",
|
||||
"url": "https://api.github.com/repos/theseer/tokenizer/zipball/75a63c33a8577608444246075ea0af0d052e452a",
|
||||
"reference": "75a63c33a8577608444246075ea0af0d052e452a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-dom": "*",
|
||||
"ext-tokenizer": "*",
|
||||
"ext-xmlwriter": "*",
|
||||
"php": "^7.0"
|
||||
"php": "^7.2 || ^8.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
|
@ -2293,24 +2293,30 @@
|
|||
}
|
||||
],
|
||||
"description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
|
||||
"time": "2019-06-13T22:48:21+00:00"
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/theseer",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2020-07-12T23:59:07+00:00"
|
||||
},
|
||||
{
|
||||
"name": "webmozart/assert",
|
||||
"version": "1.9.0",
|
||||
"version": "1.9.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/webmozart/assert.git",
|
||||
"reference": "9dc4f203e36f2b486149058bade43c851dd97451"
|
||||
"reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/webmozart/assert/zipball/9dc4f203e36f2b486149058bade43c851dd97451",
|
||||
"reference": "9dc4f203e36f2b486149058bade43c851dd97451",
|
||||
"url": "https://api.github.com/repos/webmozart/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389",
|
||||
"reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^5.3.3 || ^7.0",
|
||||
"php": "^5.3.3 || ^7.0 || ^8.0",
|
||||
"symfony/polyfill-ctype": "^1.8"
|
||||
},
|
||||
"conflict": {
|
||||
|
@ -2342,7 +2348,7 @@
|
|||
"check",
|
||||
"validate"
|
||||
],
|
||||
"time": "2020-06-16T10:16:42+00:00"
|
||||
"time": "2020-07-08T17:02:28+00:00"
|
||||
}
|
||||
],
|
||||
"aliases": [],
|
||||
|
|
Loading…
Reference in New Issue