refactor: rewrite form pages using form helper

- add installGateway to app config
- update route names and groups
- remove `author_name` and `author_email` from `episodes` table
- remove `author_name` and `author_email` from `podcasts` table
- remove `owner_id` + add `created_by` and `updated_by` fields in `podcasts` and `episodes` tables
- remove unnecessary comments in database fields
- remove confirm password inputs from auth forms for better ux
- rename `pub_date` field to `published_at` and add publication time field in episode form

closes #14, #28
This commit is contained in:
Yassine Doghri 2020-08-14 18:27:57 +00:00
parent 012de2072e
commit a1a28de702
59 changed files with 1538 additions and 1116 deletions

View File

@ -290,4 +290,12 @@ class App extends BaseConfig
| Defines a base route for all authentication related pages
*/
public $authGateway = 'cp-auth';
/*
|--------------------------------------------------------------------------
| Install gateway
|--------------------------------------------------------------------------
| Defines a base route for instance installation
*/
public $installGateway = 'cp-install';
}

View File

@ -43,24 +43,23 @@ $routes->addPlaceholder('episodeSlug', '[a-zA-Z0-9\-]{1,191}');
$routes->get('/', 'Home::index', ['as' => 'home']);
// Install Wizard route
$routes->group('cp-install', function ($routes) {
$routes->group(config('App')->installGateway, function ($routes) {
$routes->get('/', 'Install', ['as' => 'install']);
$routes->post('generate-env', 'Install::attemptCreateEnv', [
'as' => 'install_generate_env',
'as' => 'generate-env',
]);
$routes->post('create-superadmin', 'Install::attemptCreateSuperAdmin', [
'as' => 'install_create_superadmin',
'as' => 'create-superadmin',
]);
});
// Public routes
$routes->group('@(:podcastName)', function ($routes) {
$routes->get('/', 'Podcast/$1', ['as' => 'podcast']);
$routes->get('feed.xml', 'Feed/$1', ['as' => 'podcast_feed']);
$routes->get('episodes/(:episodeSlug)', 'Episode/$1/$2', [
$routes->get('(:episodeSlug)', 'Episode/$1/$2', [
'as' => 'episode',
]);
$routes->get('feed.xml', 'Feed/$1', ['as' => 'podcast_feed']);
});
// Route for podcast audio file analytics (/stats/podcast_id/episode_id/podcast_folder/filename.mp3)
@ -82,228 +81,218 @@ $routes->group(
]);
$routes->get('my-podcasts', 'Podcast::myPodcasts', [
'as' => 'my_podcasts',
]);
$routes->get('podcasts', 'Podcast::list', [
'as' => 'podcast_list',
]);
$routes->get('podcasts/new', 'Podcast::create', [
'as' => 'podcast_create',
'filter' => 'permission:podcasts-create',
]);
$routes->post('podcasts/new', 'Podcast::attemptCreate', [
'filter' => 'permission:podcasts-create',
'as' => 'my-podcasts',
]);
// Use ids in admin area to help permission and group lookups
$routes->group('podcasts/(:num)', function ($routes) {
$routes->get('/', 'Podcast::view/$1', [
'as' => 'podcast_view',
'filter' => 'permission:podcasts-view,podcast-view',
// Podcasts
$routes->group('podcasts', function ($routes) {
$routes->get('/', 'Podcast::list', [
'as' => 'podcast-list',
]);
$routes->get('edit', 'Podcast::edit/$1', [
'as' => 'podcast_edit',
'filter' => 'permission:podcasts-edit,podcast-edit',
$routes->get('new', 'Podcast::create', [
'as' => 'podcast-create',
'filter' => 'permission:podcasts-create',
]);
$routes->post('edit', 'Podcast::attemptEdit/$1', [
'filter' => 'permission:podcasts-edit,podcast-edit',
]);
$routes->add('delete', 'Podcast::delete/$1', [
'as' => 'podcast_delete',
'filter' => 'permission:podcasts-edit,podcast-delete',
$routes->post('new', 'Podcast::attemptCreate', [
'filter' => 'permission:podcasts-create',
]);
// Podcast episodes
$routes->get('episodes', 'Episode::list/$1', [
'as' => 'episode_list',
'filter' => 'permission:podcasts-view,podcast-view',
]);
$routes->get('episodes/new', 'Episode::create/$1', [
'as' => 'episode_create',
'filter' =>
'permission:episodes-create,podcast_episodes-create',
]);
$routes->post('episodes/new', 'Episode::attemptCreate/$1', [
'filter' =>
'permission:episodes-create,podcast_episodes-create',
]);
// Podcast
// Use ids in admin area to help permission and group lookups
$routes->group('(:num)', function ($routes) {
$routes->get('/', 'Podcast::view/$1', [
'as' => 'podcast-view',
'filter' => 'permission:podcasts-view,podcast-view',
]);
$routes->get('edit', 'Podcast::edit/$1', [
'as' => 'podcast-edit',
'filter' => 'permission:podcasts-edit,podcast-edit',
]);
$routes->post('edit', 'Podcast::attemptEdit/$1', [
'filter' => 'permission:podcasts-edit,podcast-edit',
]);
$routes->add('delete', 'Podcast::delete/$1', [
'as' => 'podcast-delete',
'filter' => 'permission:podcasts-edit,podcast-delete',
]);
$routes->get('episodes/(:num)', 'Episode::view/$1/$2', [
'as' => 'episode_view',
'filter' => 'permission:episodes-view,podcast_episodes-view',
]);
$routes->get('episodes/(:num)/edit', 'Episode::edit/$1/$2', [
'as' => 'episode_edit',
'filter' => 'permission:episodes-edit,podcast_episodes-edit',
]);
$routes->post(
'episodes/(:num)/edit',
'Episode::attemptEdit/$1/$2',
[
'filter' =>
'permission:episodes-edit,podcast_episodes-edit',
]
);
$routes->add('episodes/(:num)/delete', 'Episode::delete/$1/$2', [
'as' => 'episode_delete',
'filter' =>
'permission:episodes-delete,podcast_episodes-delete',
]);
// Podcast episodes
$routes->group('episodes', function ($routes) {
$routes->get('/', 'Episode::list/$1', [
'as' => 'episode-list',
'filter' => 'permission:podcasts-view,podcast-view',
]);
$routes->get('new', 'Episode::create/$1', [
'as' => 'episode-create',
'filter' =>
'permission:episodes-create,podcast_episodes-create',
]);
$routes->post('new', 'Episode::attemptCreate/$1', [
'filter' =>
'permission:episodes-create,podcast_episodes-create',
]);
// Podcast contributors
$routes->get('contributors', 'Contributor::list/$1', [
'as' => 'contributor_list',
'filter' =>
'permission:podcasts-manage_contributors,podcast-manage_contributors',
]);
$routes->get('contributors/add', 'Contributor::add/$1', [
'as' => 'contributor_add',
'filter' =>
'permission:podcasts-manage_contributors,podcast-manage_contributors',
]);
$routes->post('contributors/add', 'Contributor::attemptAdd/$1', [
'filter' =>
'permission:podcasts-manage_contributors,podcast-manage_contributors',
]);
$routes->get('contributors/(:num)', 'Contributor::view/$1/$2', [
'as' => 'contributor_view',
]);
$routes->get(
'contributors/(:num)/edit',
'Contributor::edit/$1/$2',
[
'as' => 'contributor_edit',
'filter' =>
'permission:podcasts-manage_contributors,podcast-manage_contributors',
]
);
$routes->post(
'contributors/(:num)/edit',
'Contributor::attemptEdit/$1/$2',
[
'filter' =>
'permission:podcasts-manage_contributors,podcast-manage_contributors',
]
);
$routes->add(
'contributors/(:num)/remove',
'Contributor::remove/$1/$2',
[
'as' => 'contributor_remove',
'filter' =>
'permission:podcasts-manage_contributors,podcast-manage_contributors',
]
);
// Episode
$routes->group('(:num)', function ($routes) {
$routes->get('/', 'Episode::view/$1/$2', [
'as' => 'episode-view',
'filter' =>
'permission:episodes-view,podcast_episodes-view',
]);
$routes->get('edit', 'Episode::edit/$1/$2', [
'as' => 'episode-edit',
'filter' =>
'permission:episodes-edit,podcast_episodes-edit',
]);
$routes->post('edit', 'Episode::attemptEdit/$1/$2', [
'filter' =>
'permission:episodes-edit,podcast_episodes-edit',
]);
$routes->add('delete', 'Episode::delete/$1/$2', [
'as' => 'episode-delete',
'filter' =>
'permission:episodes-delete,podcast_episodes-delete',
]);
});
});
// Podcast contributors
$routes->group('contributors', function ($routes) {
$routes->get('/', 'Contributor::list/$1', [
'as' => 'contributor-list',
'filter' =>
'permission:podcasts-manage_contributors,podcast-manage_contributors',
]);
$routes->get('add', 'Contributor::add/$1', [
'as' => 'contributor-add',
'filter' =>
'permission:podcasts-manage_contributors,podcast-manage_contributors',
]);
$routes->post('add', 'Contributor::attemptAdd/$1', [
'filter' =>
'permission:podcasts-manage_contributors,podcast-manage_contributors',
]);
// Contributor
$routes->group('(:num)', function ($routes) {
$routes->get('/', 'Contributor::view/$1/$2', [
'as' => 'contributor-view',
]);
$routes->get('edit', 'Contributor::edit/$1/$2', [
'as' => 'contributor-edit',
'filter' =>
'permission:podcasts-manage_contributors,podcast-manage_contributors',
]);
$routes->post(
'edit',
'Contributor::attemptEdit/$1/$2',
[
'filter' =>
'permission:podcasts-manage_contributors,podcast-manage_contributors',
]
);
$routes->add('remove', 'Contributor::remove/$1/$2', [
'as' => 'contributor-remove',
'filter' =>
'permission:podcasts-manage_contributors,podcast-manage_contributors',
]);
});
});
});
});
// Users
$routes->get('users', 'User::list', [
'as' => 'user_list',
'filter' => 'permission:users-list',
]);
$routes->get('users/new', 'User::create', [
'as' => 'user_create',
'filter' => 'permission:users-create',
]);
$routes->get('users/(:num)', 'User::view/$1', [
'as' => 'user_view',
'filter' => 'permission:users-view',
]);
$routes->post('users/new', 'User::attemptCreate', [
'filter' => 'permission:users-create',
]);
$routes->get('users/(:num)/edit', 'User::edit/$1', [
'as' => 'user_edit',
'filter' => 'permission:users-manage_authorizations',
]);
$routes->post('users/(:num)/edit', 'User::attemptEdit/$1', [
'filter' => 'permission:users-manage_authorizations',
]);
$routes->group('users', function ($routes) {
$routes->get('/', 'User::list', [
'as' => 'user-list',
'filter' => 'permission:users-list',
]);
$routes->get('new', 'User::create', [
'as' => 'user-create',
'filter' => 'permission:users-create',
]);
$routes->post('new', '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/(:num)/force-pass-reset',
'User::forcePassReset/$1',
[
'as' => 'user_force_pass_reset',
'filter' => 'permission:users-force_pass_reset',
]
);
$routes->add('users/(:num)/delete', 'User::delete/$1', [
'as' => 'user_delete',
'filter' => 'permission:users-delete',
]);
// User
$routes->group('(:num)', function ($routes) {
$routes->get('/', 'User::view/$1', [
'as' => 'user-view',
'filter' => 'permission:users-view',
]);
$routes->get('edit', 'User::edit/$1', [
'as' => 'user-edit',
'filter' => 'permission:users-manage_authorizations',
]);
$routes->post('edit', 'User::attemptEdit/$1', [
'filter' => 'permission:users-manage_authorizations',
]);
$routes->add('ban', 'User::ban/$1', [
'as' => 'user-ban',
'filter' => 'permission:users-manage_bans',
]);
$routes->add('unban', 'User::unBan/$1', [
'as' => 'user-unban',
'filter' => 'permission:users-manage_bans',
]);
$routes->add('force-pass-reset', 'User::forcePassReset/$1', [
'as' => 'user-force_pass_reset',
'filter' => 'permission:users-force_pass_reset',
]);
$routes->add('delete', 'User::delete/$1', [
'as' => 'user-delete',
'filter' => 'permission:users-delete',
]);
});
});
// My account
$routes->get('my-account', 'Myaccount', [
'as' => 'myAccount',
]);
$routes->get(
'my-account/change-password',
'Myaccount::changePassword/$1',
[
'as' => 'myAccount_change-password',
]
);
$routes->post(
'my-account/change-password',
'Myaccount::attemptChange/$1',
[
'as' => 'myAccount_change-password',
]
);
$routes->group('my-account', function ($routes) {
$routes->get('/', 'Myaccount', [
'as' => 'my-account',
]);
$routes->get('change-password', 'Myaccount::changePassword/$1', [
'as' => 'change-password',
]);
$routes->post('change-password', 'Myaccount::attemptChange/$1');
});
}
);
/**
* Overwriting Myth:auth routes file
*/
$routes->group(
config('App')->authGateway,
['namespace' => 'Myth\Auth\Controllers'],
function ($routes) {
// Login/out
$routes->get('login', 'AuthController::login', ['as' => 'login']);
$routes->post('login', 'AuthController::attemptLogin');
$routes->get('logout', 'AuthController::logout', ['as' => 'logout']);
$routes->group(config('App')->authGateway, function ($routes) {
// Login/out
$routes->get('login', 'Auth::login', ['as' => 'login']);
$routes->post('login', 'Auth::attemptLogin');
$routes->get('logout', 'Auth::logout', ['as' => 'logout']);
// Registration
$routes->get('register', 'AuthController::register', [
'as' => 'register',
]);
$routes->post('register', 'AuthController::attemptRegister');
// Registration
$routes->get('register', 'Auth::register', [
'as' => 'register',
]);
$routes->post('register', 'Auth::attemptRegister');
// Activation
$routes->get('activate-account', 'AuthController::activateAccount', [
'as' => 'activate-account',
]);
$routes->get(
'resend-activate-account',
'AuthController::resendActivateAccount',
[
'as' => 'resend-activate-account',
]
);
// Activation
$routes->get('activate-account', 'Auth::activateAccount', [
'as' => 'activate-account',
]);
$routes->get('resend-activate-account', 'Auth::resendActivateAccount', [
'as' => 'resend-activate-account',
]);
// Forgot/Resets
$routes->get('forgot', 'AuthController::forgotPassword', [
'as' => 'forgot',
]);
$routes->post('forgot', 'AuthController::attemptForgot');
$routes->get('reset-password', 'AuthController::resetPassword', [
'as' => 'reset-password',
]);
$routes->post('reset-password', 'AuthController::attemptReset');
}
);
// Forgot/Resets
$routes->get('forgot', 'Auth::forgotPassword', [
'as' => 'forgot',
]);
$routes->post('forgot', 'Auth::attemptForgot');
$routes->get('reset-password', 'Auth::resetPassword', [
'as' => 'reset-password',
]);
$routes->post('reset-password', 'Auth::attemptReset');
});
/**
* --------------------------------------------------------------------

View File

@ -70,10 +70,32 @@ class Contributor extends BaseController
public function add()
{
helper('form');
$users = (new UserModel())->findAll();
$userOptions = array_reduce(
$users,
function ($result, $user) {
$result[$user->id] = $user->username;
return $result;
},
[]
);
$roles = (new GroupModel())->getContributorRoles();
$roleOptions = array_reduce(
$roles,
function ($result, $role) {
$result[$role->id] = lang('Contributor.roles.' . $role->name);
return $result;
},
[]
);
$data = [
'podcast' => $this->podcast,
'users' => (new UserModel())->findAll(),
'roles' => (new GroupModel())->getContributorRoles(),
'userOptions' => $userOptions,
'roleOptions' => $roleOptions,
];
replace_breadcrumb_params([0 => $this->podcast->title]);
@ -97,11 +119,23 @@ class Contributor extends BaseController
]);
}
return redirect()->route('contributor_list', [$this->podcast->id]);
return redirect()->route('contributor-list', [$this->podcast->id]);
}
public function edit()
{
helper('form');
$roles = (new GroupModel())->getContributorRoles();
$roleOptions = array_reduce(
$roles,
function ($result, $role) {
$result[$role->id] = lang('Contributor.roles.' . $role->name);
return $result;
},
[]
);
$data = [
'podcast' => $this->podcast,
'user' => $this->user,
@ -109,7 +143,7 @@ class Contributor extends BaseController
$this->user->id,
$this->podcast->id
),
'roles' => (new GroupModel())->getContributorRoles(),
'roleOptions' => $roleOptions,
];
replace_breadcrumb_params([
@ -127,7 +161,7 @@ class Contributor extends BaseController
$this->request->getPost('role')
);
return redirect()->route('contributor_list', [$this->podcast->id]);
return redirect()->route('contributor-list', [$this->podcast->id]);
}
public function remove()

View File

@ -86,6 +86,9 @@ class Episode extends BaseController
'enclosure' => 'uploaded[enclosure]|ext_in[enclosure,mp3,m4a]',
'image' =>
'uploaded[image]|is_image[image]|ext_in[image,jpg,png]|permit_empty',
'publication_date' => 'valid_date[Y-m-d]|permit_empty',
'publication_time' =>
'regex_match[/^(0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$/]|permit_empty',
];
if (!$this->validate($rules)) {
@ -100,17 +103,20 @@ class Episode extends BaseController
'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'),
'explicit' => $this->request->getPost('explicit') == 'yes',
'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'),
'block' => $this->request->getPost('block') == 'yes',
'created_by' => user(),
'updated_by' => user(),
]);
$newEpisode->setPublishedAt(
$this->request->getPost('publication_date'),
$this->request->getPost('publication_time')
);
$episodeModel = new EpisodeModel();
@ -121,7 +127,7 @@ class Episode extends BaseController
->with('errors', $episodeModel->errors());
}
return redirect()->route('episode_list', [$this->podcast->id]);
return redirect()->route('episode-list', [$this->podcast->id]);
}
public function edit()
@ -146,6 +152,9 @@ class Episode extends BaseController
'uploaded[enclosure]|ext_in[enclosure,mp3,m4a]|permit_empty',
'image' =>
'uploaded[image]|is_image[image]|ext_in[image,jpg,png]|permit_empty',
'publication_date' => 'valid_date[Y-m-d]|permit_empty',
'publication_time' =>
'regex_match[/^(0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$/]|permit_empty',
];
if (!$this->validate($rules)) {
@ -157,17 +166,19 @@ class Episode extends BaseController
$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->explicit = $this->request->getPost('explicit') == 'yes';
$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');
$this->episode->block = $this->request->getPost('block') == 'yes';
$this->episode->setPublishedAt(
$this->request->getPost('publication_date'),
$this->request->getPost('publication_time')
);
$this->episode->updated_by = user();
$enclosure = $this->request->getFile('enclosure');
if ($enclosure->isValid()) {
@ -187,13 +198,13 @@ class Episode extends BaseController
->with('errors', $episodeModel->errors());
}
return redirect()->route('episode_list', [$this->podcast->id]);
return redirect()->route('episode-list', [$this->podcast->id]);
}
public function delete()
{
(new EpisodeModel())->delete($this->episode->id);
return redirect()->route('episode_list', [$this->podcast->id]);
return redirect()->route('episode-list', [$this->podcast->id]);
}
}

View File

@ -20,6 +20,8 @@ class Myaccount extends BaseController
public function changePassword()
{
helper('form');
return view('admin/my_account/change_password');
}
@ -31,10 +33,8 @@ class Myaccount extends BaseController
// Validate here first, since some things,
// like the password, can only be validated properly here.
$rules = [
'email' => 'required|valid_email',
'password' => 'required',
'new_password' => 'required|strong_password',
'new_pass_confirm' => 'required|matches[new_password]',
'new_password' => 'required|strong_password|differs[password]',
];
if (!$this->validate($rules)) {
@ -53,7 +53,7 @@ class Myaccount extends BaseController
return redirect()
->back()
->withInput()
->with('errors', $userModel->errors());
->with('error', lang('MyAccount.messages.wrongPasswordError'));
}
user()->password = $this->request->getPost('new_password');
@ -68,7 +68,7 @@ class Myaccount extends BaseController
// Success!
return redirect()
->route('myAccount')
->back()
->with('message', lang('MyAccount.messages.passwordChangeSuccess'));
}
}

View File

@ -43,7 +43,7 @@ class Podcast extends BaseController
public function list()
{
if (!has_permission('podcasts-list')) {
return redirect()->route('my_podcasts');
return redirect()->route('my-podcasts');
}
$data = ['podcasts' => (new PodcastModel())->findAll()];
@ -63,11 +63,30 @@ class Podcast extends BaseController
{
helper(['form', 'misc']);
$languageModel = new LanguageModel();
$categoryModel = new CategoryModel();
$categories = (new CategoryModel())->findAll();
$languages = (new LanguageModel())->findAll();
$languageOptions = array_reduce(
$languages,
function ($result, $language) {
$result[$language->code] = $language->native_name;
return $result;
},
[]
);
$categoryOptions = array_reduce(
$categories,
function ($result, $category) {
$result[$category->code] = lang(
'Podcast.category_options.' . $category->code
);
return $result;
},
[]
);
$data = [
'languages' => $languageModel->findAll(),
'categories' => $categoryModel->findAll(),
'languageOptions' => $languageOptions,
'categoryOptions' => $categoryOptions,
'browserLang' => get_browser_language(
$this->request->getServer('HTTP_ACCEPT_LANGUAGE')
),
@ -99,17 +118,17 @@ class Podcast extends BaseController
'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(),
'explicit' => $this->request->getPost('explicit') == 'yes',
'author' => $this->request->getPost('author'),
'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'),
'block' => $this->request->getPost('block') == 'yes',
'complete' => $this->request->getPost('complete') == 'yes',
'custom_html_head' => $this->request->getPost('custom_html_head'),
'created_by' => user(),
'updated_by' => user(),
]);
$podcastModel = new PodcastModel();
@ -136,17 +155,38 @@ class Podcast extends BaseController
$db->transComplete();
return redirect()->route('podcast_list');
return redirect()->route('podcast-list');
}
public function edit()
{
helper('form');
$categories = (new CategoryModel())->findAll();
$languages = (new LanguageModel())->findAll();
$languageOptions = array_reduce(
$languages,
function ($result, $language) {
$result[$language->code] = $language->native_name;
return $result;
},
[]
);
$categoryOptions = array_reduce(
$categories,
function ($result, $category) {
$result[$category->code] = lang(
'Podcast.category_options.' . $category->code
);
return $result;
},
[]
);
$data = [
'podcast' => $this->podcast,
'languages' => (new LanguageModel())->findAll(),
'categories' => (new CategoryModel())->findAll(),
'languageOptions' => $languageOptions,
'categoryOptions' => $categoryOptions,
];
replace_breadcrumb_params([0 => $this->podcast->title]);
@ -180,18 +220,18 @@ class Podcast extends BaseController
}
$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->explicit = $this->request->getPost('explicit') == 'yes';
$this->podcast->author = $this->request->getPost('author');
$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->block = $this->request->getPost('block') == 'yes';
$this->podcast->complete = $this->request->getPost('complete') == 'yes';
$this->podcast->custom_html_head = $this->request->getPost(
'custom_html_head'
);
$this->updated_by = user();
$podcastModel = new PodcastModel();
@ -202,13 +242,13 @@ class Podcast extends BaseController
->with('errors', $podcastModel->errors());
}
return redirect()->route('podcast_list');
return redirect()->route('podcast-list');
}
public function delete()
{
(new PodcastModel())->delete($this->podcast->id);
return redirect()->route('podcast_list');
return redirect()->route('podcast-list');
}
}

View File

@ -47,6 +47,8 @@ class User extends BaseController
public function create()
{
helper('form');
$data = [
'roles' => (new GroupModel())->getUserRoles(),
];
@ -65,7 +67,6 @@ class User extends BaseController
[
'email' => 'required|valid_email|is_unique[users.email]',
'password' => 'required|strong_password',
'pass_confirm' => 'required|matches[password]',
]
);
@ -94,7 +95,7 @@ class User extends BaseController
// Success!
return redirect()
->route('user_list')
->route('user-list')
->with(
'message',
lang('User.messages.createSuccess', [
@ -105,9 +106,21 @@ class User extends BaseController
public function edit()
{
helper('form');
$roles = (new GroupModel())->getUserRoles();
$roleOptions = array_reduce(
$roles,
function ($result, $role) {
$result[$role->name] = lang('User.roles.' . $role->name);
return $result;
},
[]
);
$data = [
'user' => $this->user,
'roles' => (new GroupModel())->getUserRoles(),
'roleOptions' => $roleOptions,
];
replace_breadcrumb_params([0 => $this->user->username]);
@ -123,7 +136,7 @@ class User extends BaseController
// Success!
return redirect()
->route('user_list')
->route('user-list')
->with(
'message',
lang('User.messages.rolesEditSuccess', [
@ -145,7 +158,7 @@ class User extends BaseController
// Success!
return redirect()
->route('user_list')
->route('user-list')
->with(
'message',
lang('User.messages.forcePassResetSuccess', [
@ -178,7 +191,7 @@ class User extends BaseController
}
return redirect()
->route('user_list')
->route('user-list')
->with(
'message',
lang('User.messages.banSuccess', [
@ -199,7 +212,7 @@ class User extends BaseController
}
return redirect()
->route('user_list')
->route('user-list')
->with(
'message',
lang('User.messages.unbanSuccess', [

167
app/Controllers/Auth.php Normal file
View File

@ -0,0 +1,167 @@
<?php
/**
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Controllers;
use App\Entities\User;
class Auth extends \Myth\Auth\Controllers\AuthController
{
/**
* Attempt to register a new user.
*/
public function attemptRegister()
{
// Check if registration is allowed
if (!$this->config->allowRegistration) {
return redirect()
->back()
->withInput()
->with('error', lang('Auth.registerDisabled'));
}
$users = model('UserModel');
// Validate here first, since some things,
// like the password, can only be validated properly here.
$rules = [
'username' =>
'required|alpha_numeric_space|min_length[3]|is_unique[users.username]',
'email' => 'required|valid_email|is_unique[users.email]',
'password' => 'required|strong_password',
];
if (!$this->validate($rules)) {
return redirect()
->back()
->withInput()
->with('errors', service('validation')->getErrors());
}
// Save the user
$allowedPostFields = array_merge(
['password'],
$this->config->validFields,
$this->config->personalFields
);
$user = new User($this->request->getPost($allowedPostFields));
$this->config->requireActivation !== false
? $user->generateActivateHash()
: $user->activate();
// Ensure default group gets assigned if set
if (!empty($this->config->defaultUserGroup)) {
$users = $users->withGroup($this->config->defaultUserGroup);
}
if (!$users->save($user)) {
return redirect()
->back()
->withInput()
->with('errors', $users->errors());
}
if ($this->config->requireActivation !== false) {
$activator = service('activator');
$sent = $activator->send($user);
if (!$sent) {
return redirect()
->back()
->withInput()
->with(
'error',
$activator->error() ?? lang('Auth.unknownError')
);
}
// Success!
return redirect()
->route('login')
->with('message', lang('Auth.activationSuccess'));
}
// Success!
return redirect()
->route('login')
->with('message', lang('Auth.registerSuccess'));
}
/**
* Verifies the code with the email and saves the new password,
* if they all pass validation.
*
* @return mixed
*/
public function attemptReset()
{
if ($this->config->activeResetter === false) {
return redirect()
->route('login')
->with('error', lang('Auth.forgotDisabled'));
}
$users = model('UserModel');
// First things first - log the reset attempt.
$users->logResetAttempt(
$this->request->getPost('email'),
$this->request->getPost('token'),
$this->request->getIPAddress(),
(string) $this->request->getUserAgent()
);
$rules = [
'token' => 'required',
'email' => 'required|valid_email',
'password' => 'required|strong_password',
];
if (!$this->validate($rules)) {
return redirect()
->back()
->withInput()
->with('errors', $users->errors());
}
$user = $users
->where('email', $this->request->getPost('email'))
->where('reset_hash', $this->request->getPost('token'))
->first();
if (is_null($user)) {
return redirect()
->back()
->with('error', lang('Auth.forgotNoUser'));
}
// Reset token still valid?
if (
!empty($user->reset_expires) &&
time() > $user->reset_expires->getTimestamp()
) {
return redirect()
->back()
->withInput()
->with('error', lang('Auth.resetTokenExpired'));
}
// Success! Save the new password, and cleanup the reset hash.
$user->password = $this->request->getPost('password');
$user->reset_hash = null;
$user->reset_at = date('Y-m-d H:i:s');
$user->reset_expires = null;
$user->force_pass_reset = false;
$users->save($user);
return redirect()
->route('login')
->with('message', lang('Auth.resetSuccess'));
}
}

View File

@ -30,7 +30,6 @@ class AddLanguages extends Migration
],
'native_name' => [
'type' => 'VARCHAR',
'comment' => 'Native language name.',
'constraint' => 191,
],
]);

View File

@ -23,125 +23,90 @@ class AddPodcasts extends Migration
'constraint' => 20,
'unsigned' => true,
'auto_increment' => true,
'comment' => 'The podcast ID',
],
'title' => [
'type' => 'VARCHAR',
'constraint' => 1024,
'comment' =>
'The show title. Its important to have a clear, concise name for your podcast. Make your title specific. A show titled Our Community Bulletin is too vague to attract many subscribers, no matter how compelling the content. Pay close attention to the title as Apple Podcasts uses this field for search. If you include a long list of keywords in an attempt to game podcast search, your show may be removed from the Apple directory.',
],
'name' => [
'type' => 'VARCHAR',
'constraint' => 191,
'unique' => true,
'comment' => 'Unique podcast string identifier.',
],
'description' => [
'type' => 'TEXT',
'comment' =>
'The show description. Where description is text containing one or more sentences describing your podcast to potential listeners. The maximum amount of text allowed for this tag is 4000 characters. To include links in your description or rich HTML, adhere to the following technical guidelines: enclose all portions of your XML that contain embedded HTML in a CDATA section to prevent formatting issues, and to ensure proper link functionality.',
],
'image_uri' => [
'type' => 'VARCHAR',
'constraint' => 1024,
'comment' =>
'The artwork for the show. Specify your show artwork by providing a URL linking to it. Depending on their device, subscribers see your podcast artwork in varying sizes. Therefore, make sure your design is effective at both its original size and at thumbnail size. You should include a show title, brand, or source name as part of your podcast artwork. Artwork must be a minimum size of 1400 x 1400 pixels and a maximum size of 3000 x 3000 pixels, in JPEG or PNG format, 72 dpi, with appropriate file extensions (.jpg, .png), and in the RGB colorspace.',
],
'language' => [
'type' => 'VARCHAR',
'constraint' => 2,
'comment' =>
'The language spoken on the show. Because Apple Podcasts is available in territories around the world, it is critical to specify the language of a podcast. Apple Podcasts only supports values from the ISO 639 list (two-letter language codes, with some possible modifiers, such as "en-us"). Invalid language codes will cause your feed to fail Apple validation.',
],
'category' => [
'type' => 'VARCHAR',
'constraint' => 1024,
'comment' =>
'The show category information. For a complete list of categories and subcategories, see Apple Podcasts categories. Select the category that best reflects the content of your show. If available, you can also define a subcategory. Although you can specify more than one category and subcategory in your RSS feed, Apple Podcasts only recognizes the first category and subcategory. When specifying categories and subcategories, be sure to properly escape ampersands.',
'null' => true,
],
'explicit' => [
'type' => 'TINYINT',
'constraint' => 1,
'default' => 0,
'comment' =>
'The podcast parental advisory information. The explicit value can be one of the following: True: If you specify true, indicating the presence of explicit content, Apple Podcasts displays an Explicit parental advisory graphic for your podcast. Podcasts containing explicit material arent available in some Apple Podcasts territories. False: If you specify false, indicating that your podcast doesnt contain explicit language or adult content, Apple Podcasts displays a Clean parental advisory graphic for your podcast.',
],
'author_name' => [
'author' => [
'type' => 'VARCHAR',
'constraint' => 1024,
'comment' =>
'Name 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,
],
'author_email' => [
'type' => 'VARCHAR',
'constraint' => 1024,
'owner_email' =>
'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,
'comment' =>
'The podcast owner name. Note: The owner information is for administrative communication about the podcast and isnt displayed in Apple Podcasts.',
'null' => true,
],
'owner_email' => [
'type' => 'VARCHAR',
'constraint' => 1024,
'comment' =>
'The podcast owner email address. Note: The owner information is for administrative communication about the podcast and isnt displayed in Apple Podcasts. Please make sure the email address is active and monitored.',
'null' => true,
],
'type' => [
'type' => 'ENUM',
'constraint' => ['episodic', 'serial'],
'default' => 'episodic',
'comment' =>
'The type of show. If your show is Serial you must use this tag. Its values can be one of the following: episodic (default). Specify episodic when episodes are intended to be consumed without any specific order. Apple Podcasts will present newest episodes first and display the publish date (required) of each episode. If organized into seasons, the newest season will be presented first - otherwise, episodes will be grouped by year published, newest first. For new subscribers, Apple Podcasts adds the newest, most recent episode in their Library. serial. Specify serial when episodes are intended to be consumed in sequential order. Apple Podcasts will present the oldest episodes first and display the episode numbers (required) of each episode. If organized into seasons, the newest season will be presented first and <itunes:episode> numbers must be given for each episode. For new subscribers, Apple Podcasts adds the first episode to their Library, or the entire current season if using seasons',
],
'copyright' => [
'type' => 'VARCHAR',
'constraint' => 1024,
'comment' =>
'The show copyright details. If your show is copyrighted you should use this tag.',
'null' => true,
],
'block' => [
'type' => 'TINYINT',
'constraint' => 1,
'default' => 0,
'comment' =>
'The podcast show or hide status. If you want your show removed from the Apple directory, use this tag. Specifying the <itunes:block> tag with a Yes value, prevents the entire podcast from appearing in Apple Podcasts. Specifying any value other than Yes has no effect.',
],
'complete' => [
'type' => 'TINYINT',
'constraint' => 1,
'default' => 0,
'comment' =>
'The podcast update status. If you will never publish another episode to your show, use this tag. Specifying the <itunes:complete> tag with a Yes value indicates that a podcast is complete and you will not post any more episodes in the future. Specifying any value other than Yes has no effect.',
],
'episode_description_footer' => [
'type' => 'TEXT',
'comment' =>
'The text that will be added in every episode description (show notes).',
'null' => true,
],
'custom_html_head' => [
'type' => 'TEXT',
'comment' =>
'The HTML code that will be added to every page for this podcast. (You could add Google Analytics tracking code here for instance.)',
'null' => true,
],
'created_by' => [
'type' => 'INT',
'constraint' => 11,
'unsigned' => true,
],
'updated_by' => [
'type' => 'INT',
'constraint' => 11,
'unsigned' => true,
],
'created_at' => [
'type' => 'TIMESTAMP',
],
@ -154,7 +119,8 @@ class AddPodcasts extends Migration
],
]);
$this->forge->addKey('id', true);
$this->forge->addForeignKey('owner_id', 'users', 'id');
$this->forge->addForeignKey('created_by', 'users', 'id');
$this->forge->addForeignKey('updated_by', 'users', 'id');
$this->forge->createTable('podcasts');
}

View File

@ -23,98 +23,73 @@ class AddEpisodes extends Migration
'constraint' => 20,
'unsigned' => true,
'auto_increment' => true,
'comment' => 'The episode ID',
],
'podcast_id' => [
'type' => 'BIGINT',
'constraint' => 20,
'unsigned' => true,
'comment' => 'The podcast ID',
],
'title' => [
'type' => 'VARCHAR',
'constraint' => 1024,
'comment' =>
'An episode title. title is a string containing a clear, concise name for your episode. Dont specify the episode number or season number in this tag.',
],
'slug' => [
'type' => 'VARCHAR',
'constraint' => 191,
'comment' => 'Episode slug for URLs',
],
'enclosure_uri' => [
'type' => 'VARCHAR',
'constraint' => 1024,
'comment' =>
'The URI attribute points to your podcast media file. The file extension specified within the URI attribute determines whether or not content appears in the podcast directory. Supported file formats include M4A, MP3, MOV, MP4, M4V, and PDF.',
],
'pub_date' => [
'type' => 'DATETIME',
'comment' =>
'The date and time when an episode was released. Format the date using the RFC 2822 specifications. For example: Wed, 15 Jun 2019 19:00:00 UTC.',
],
'description' => [
'type' => 'TEXT',
'null' => true,
'comment' =>
'An episode description. Description is text containing one or more sentences describing your episode to potential listeners. You can specify up to 4000 characters. You can use rich text formatting and some HTML (<p>, <ol>, <ul>, <li>, <a>) if wrapped in the <CDATA> tag. To include links in your description or rich HTML, adhere to the following technical guidelines: enclose all portions of your XML that contain embedded HTML in a CDATA section to prevent formatting issues, and to ensure proper link functionality.',
],
'image_uri' => [
'type' => 'VARCHAR',
'constraint' => 1024,
'null' => true,
'comment' =>
'The artwork for the show. Specify your show artwork by providing a URL linking to it. Depending on their device, subscribers see your podcast artwork in varying sizes. Therefore, make sure your design is effective at both its original size and at thumbnail size. You should include a show title, brand, or source name as part of your podcast artwork. Artwork must be a minimum size of 1400 x 1400 pixels and a maximum size of 3000 x 3000 pixels, in JPEG or PNG format, 72 dpi, with appropriate file extensions (.jpg, .png), and in the RGB colorspace.',
],
'explicit' => [
'type' => 'TINYINT',
'constraint' => 1,
'default' => 0,
'comment' =>
'The episode parental advisory information. Where the explicit value can be one of the following: true. If you specify true, indicating the presence of explicit content, Apple Podcasts displays an Explicit parental advisory graphic for your episode. Episodes containing explicit material arent available in some Apple Podcasts territories. false. If you specify false, indicating that the episode does not contain explicit language or adult content, Apple Podcasts displays a Clean parental advisory graphic for your episode.',
],
'number' => [
'type' => 'INT',
'constraint' => 10,
'unsigned' => true,
'comment' =>
'An episode number. If all your episodes have numbers and you would like them to be ordered based on them use this tag for each one. Episode numbers are optional for <itunes:type> episodic shows, but are mandatory for serial shows. Where episode is a non-zero integer (1, 2, 3, etc.) representing your episode number.',
],
'season_number' => [
'type' => 'INT',
'constraint' => 10,
'unsigned' => true,
'default' => 1,
'comment' =>
'The episode season number. If an episode is within a season use this tag. Where season is a non-zero integer (1, 2, 3, etc.) representing your season number. To allow the season feature for shows containing a single season, if only one season exists in the RSS feed, Apple Podcasts doesnt display a season number. When you add a second season to the RSS feed, Apple Podcasts displays the season numbers.',
],
'author_name' => [
'type' => 'VARCHAR',
'constraint' => 1024,
'comment' =>
'Name of the group responsible for creating the episode. Episode 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 episodes created by the same entity.',
'null' => true,
],
'author_email' => [
'type' => 'VARCHAR',
'constraint' => 1024,
'owner_email' =>
'Email of the group responsible for creating the episode. Episode 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 episodes created by the same entity.',
'null' => true,
],
'type' => [
'type' => 'ENUM',
'constraint' => ['full', 'trailer', 'bonus'],
'default' => 'full',
'comment' =>
'The episode type. If an episode is a trailer or bonus content, use this tag. Where the episodeType value can be one of the following: full (default). Specify full when you are submitting the complete content of your show. trailer. Specify trailer when you are submitting a short, promotional piece of content that represents a preview of your current show. bonus. Specify bonus when you are submitting extra content for your show (for example, behind the scenes information or interviews with the cast) or cross-promotional content for another show.',
],
'block' => [
'type' => 'TINYINT',
'constraint' => 1,
'default' => 0,
'comment' =>
'The episode show or hide status. If you want an episode removed from the Apple directory, use this tag. Specifying the <itunes:block> tag with a Yes value prevents that episode from appearing in Apple Podcasts. For example, you might want to block a specific episode if you know that its content would otherwise cause the entire podcast to be removed from Apple Podcasts. Specifying any value other than Yes has no effect.',
],
'created_by' => [
'type' => 'INT',
'constraint' => 11,
'unsigned' => true,
],
'updated_by' => [
'type' => 'INT',
'constraint' => 11,
'unsigned' => true,
],
'published_at' => [
'type' => 'DATETIME',
'null' => true,
],
'created_at' => [
'type' => 'TIMESTAMP',
@ -132,6 +107,8 @@ class AddEpisodes extends Migration
$this->forge->addUniqueKey(['podcast_id', 'season_number', 'number']);
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id');
$this->forge->addForeignKey('created_by', 'users', 'id');
$this->forge->addForeignKey('updated_by', 'users', 'id');
$this->forge->createTable('episodes');
}

View File

@ -23,66 +23,53 @@ class AddPlatforms extends Migration
'constraint' => 20,
'unsigned' => true,
'auto_increment' => true,
'comment' => 'The platform ID',
],
'name' => [
'type' => 'VARCHAR',
'constraint' => 191,
'unique' => true,
'comment' => 'Platform name.',
],
'home_url' => [
'type' => 'VARCHAR',
'constraint' => 191,
'comment' => 'Platform home URL.',
],
'submit_url' => [
'type' => 'VARCHAR',
'constraint' => 191,
'comment' => 'Platform URL to submit podcasts.',
'null' => true,
],
'iosapp_url' => [
'type' => 'VARCHAR',
'constraint' => 191,
'comment' => 'Platform iOS app URL (if any).',
'null' => true,
],
'androidapp_url' => [
'type' => 'VARCHAR',
'constraint' => 191,
'comment' => 'Platform Android app URL (if any).',
'null' => true,
],
'comment' => [
'type' => 'TEXT',
'comment' => 'Comment.',
'null' => true,
],
'display_by_default' => [
'type' => 'TINYINT',
'constraint' => 1,
'default' => 0,
'comment' =>
'True if the platform link should be displayed by default.',
],
'ios_deeplink' => [
'type' => 'TINYINT',
'constraint' => 1,
'default' => 0,
'comment' => 'iOS deeplinking for this platform.',
],
'android_deeplink' => [
'type' => 'TINYINT',
'constraint' => 1,
'default' => 0,
'comment' =>
'Android deeplinking for this platform: 0=No, 1=Manual, 2=Automatic.',
],
'logo_file_name' => [
'type' => 'VARCHAR',
'constraint' => 1024,
'comment' => 'The logo for this platform.',
],
'created_at' => [
'type' => 'TIMESTAMP',

View File

@ -23,24 +23,20 @@ class AddPlatformLinks extends Migration
'constraint' => 20,
'unsigned' => true,
'auto_increment' => true,
'comment' => 'The link ID',
],
'podcast_id' => [
'type' => 'BIGINT',
'constraint' => 20,
'unsigned' => true,
'comment' => 'The podcast ID',
],
'platform_id' => [
'type' => 'BIGINT',
'constraint' => 20,
'unsigned' => true,
'comment' => 'The platform ID',
],
'link_url' => [
'type' => 'VARCHAR',
'constraint' => 191,
'comment' => 'Podcast link URL on this platform.',
],
'comment' => [
'type' => 'TEXT',
@ -51,7 +47,6 @@ class AddPlatformLinks extends Migration
'type' => 'TINYINT',
'constraint' => 1,
'default' => 0,
'comment' => 'Show this link.',
],
'created_at' => [
'type' => 'TIMESTAMP',

View File

@ -22,34 +22,28 @@ class AddAnalyticsEpisodesByCountry extends Migration
'constraint' => 20,
'unsigned' => true,
'auto_increment' => true,
'comment' => 'The line ID',
],
'podcast_id' => [
'type' => 'BIGINT',
'constraint' => 20,
'unsigned' => true,
'comment' => 'The podcast ID',
],
'episode_id' => [
'type' => 'BIGINT',
'constraint' => 20,
'unsigned' => true,
'comment' => 'The episode ID',
],
'country_code' => [
'type' => 'VARCHAR',
'constraint' => 3,
'comment' => 'ISO 3166-1 code.',
],
'date' => [
'type' => 'date',
'comment' => 'Line date.',
],
'hits' => [
'type' => 'INT',
'constraint' => 10,
'default' => 1,
'comment' => 'Number of hits.',
],
]);
$this->forge->addKey('id', true);

View File

@ -22,34 +22,28 @@ class AddAnalyticsEpisodesByPlayer extends Migration
'constraint' => 20,
'unsigned' => true,
'auto_increment' => true,
'comment' => 'The line ID',
],
'podcast_id' => [
'type' => 'BIGINT',
'constraint' => 20,
'unsigned' => true,
'comment' => 'The podcast ID',
],
'episode_id' => [
'type' => 'BIGINT',
'constraint' => 20,
'unsigned' => true,
'comment' => 'The episode ID',
],
'player' => [
'type' => 'VARCHAR',
'constraint' => 191,
'comment' => 'Podcast player name.',
],
'date' => [
'type' => 'date',
'comment' => 'Line date.',
],
'hits' => [
'type' => 'INT',
'constraint' => 10,
'default' => 1,
'comment' => 'Number of hits.',
],
]);
$this->forge->addKey('id', true);

View File

@ -22,13 +22,11 @@ class AddAnalyticsPodcastsByCountry extends Migration
'constraint' => 20,
'unsigned' => true,
'auto_increment' => true,
'comment' => 'The line ID',
],
'podcast_id' => [
'type' => 'BIGINT',
'constraint' => 20,
'unsigned' => true,
'comment' => 'The podcast ID',
],
'country_code' => [
'type' => 'VARCHAR',
@ -37,13 +35,11 @@ class AddAnalyticsPodcastsByCountry extends Migration
],
'date' => [
'type' => 'date',
'comment' => 'Line date.',
],
'hits' => [
'type' => 'INT',
'constraint' => 10,
'default' => 1,
'comment' => 'Number of hits.',
],
]);
$this->forge->addKey('id', true);

View File

@ -22,28 +22,23 @@ class AddAnalyticsPodcastsByPlayer extends Migration
'constraint' => 20,
'unsigned' => true,
'auto_increment' => true,
'comment' => 'The line ID',
],
'podcast_id' => [
'type' => 'BIGINT',
'constraint' => 20,
'unsigned' => true,
'comment' => 'The podcast ID',
],
'player' => [
'type' => 'VARCHAR',
'constraint' => 191,
'comment' => 'Podcast player name.',
],
'date' => [
'type' => 'date',
'comment' => 'Line date.',
],
'hits' => [
'type' => 'INT',
'constraint' => 10,
'default' => 1,
'comment' => 'Number of hits.',
],
]);
$this->forge->addKey('id', true);

View File

@ -22,19 +22,16 @@ class AddAnalyticsUnknownUseragents extends Migration
'constraint' => 20,
'unsigned' => true,
'auto_increment' => true,
'comment' => 'The line ID',
],
'useragent' => [
'type' => 'VARCHAR',
'constraint' => 191,
'unique' => true,
'comment' => 'The unknown user-agent.',
],
'hits' => [
'type' => 'INT',
'constraint' => 10,
'default' => 1,
'comment' => 'Number of hits.',
],
]);
$this->forge->addKey('id', true);

View File

@ -22,28 +22,23 @@ class AddAnalyticsWebsiteByBrowser extends Migration
'constraint' => 20,
'unsigned' => true,
'auto_increment' => true,
'comment' => 'The line ID',
],
'podcast_id' => [
'type' => 'BIGINT',
'constraint' => 20,
'unsigned' => true,
'comment' => 'The podcast ID',
],
'browser' => [
'type' => 'VARCHAR',
'constraint' => 191,
'comment' => 'The Web Browser.',
],
'date' => [
'type' => 'date',
'comment' => 'Line date.',
],
'hits' => [
'type' => 'INT',
'constraint' => 10,
'default' => 1,
'comment' => 'Number of hits.',
],
]);
$this->forge->addKey('id', true);

View File

@ -22,13 +22,11 @@ class AddAnalyticsWebsiteByCountry extends Migration
'constraint' => 20,
'unsigned' => true,
'auto_increment' => true,
'comment' => 'The line ID',
],
'podcast_id' => [
'type' => 'BIGINT',
'constraint' => 20,
'unsigned' => true,
'comment' => 'The podcast ID',
],
'country_code' => [
'type' => 'VARCHAR',
@ -37,13 +35,11 @@ class AddAnalyticsWebsiteByCountry extends Migration
],
'date' => [
'type' => 'date',
'comment' => 'Line date.',
],
'hits' => [
'type' => 'INT',
'constraint' => 10,
'default' => 1,
'comment' => 'Number of hits.',
],
]);
$this->forge->addKey('id', true);

View File

@ -22,13 +22,11 @@ class AddAnalyticsWebsiteByReferer extends Migration
'constraint' => 20,
'unsigned' => true,
'auto_increment' => true,
'comment' => 'The line ID',
],
'podcast_id' => [
'type' => 'BIGINT',
'constraint' => 20,
'unsigned' => true,
'comment' => 'The podcast ID',
],
'referer' => [
'type' => 'VARCHAR',
@ -37,13 +35,11 @@ class AddAnalyticsWebsiteByReferer extends Migration
],
'date' => [
'type' => 'date',
'comment' => 'Line date.',
],
'hits' => [
'type' => 'INT',
'constraint' => 10,
'default' => 1,
'comment' => 'Number of hits.',
],
]);
$this->forge->addKey('id', true);

View File

@ -69,20 +69,26 @@ class Episode extends Entity
*/
protected $description_html;
protected $dates = [
'published_at',
'created_at',
'updated_at',
'deleted_at',
];
protected $casts = [
'slug' => 'string',
'title' => 'string',
'enclosure_uri' => 'string',
'pub_date' => 'datetime',
'description' => 'string',
'image_uri' => '?string',
'author_name' => '?string',
'author_email' => '?string',
'explicit' => 'boolean',
'number' => 'integer',
'season_number' => '?integer',
'type' => 'string',
'block' => 'boolean',
'created_by' => 'integer',
'updated_by' => 'integer',
];
public function setImage(?\CodeIgniter\HTTP\Files\UploadedFile $image)
@ -216,4 +222,29 @@ class Episode extends Entity
return $converter->convertToHtml($this->attributes['description']);
}
public function setPublishedAt($date, $time)
{
if (empty($date)) {
$this->attributes['published_at'] = null;
} else {
$this->attributes['published_at'] = $date . ' ' . $time;
}
return $this;
}
public function setCreatedBy(\App\Entities\User $user)
{
$this->attributes['created_by'] = $user->id;
return $this;
}
public function setUpdatedBy(\App\Entities\User $user)
{
$this->attributes['updated_by'] = $user->id;
return $this;
}
}

View File

@ -40,11 +40,6 @@ class Podcast extends Entity
*/
protected $episodes;
/**
* @var
*/
protected $owner;
/**
* @var \App\Entities\User[]
*/
@ -64,9 +59,7 @@ class Podcast extends Entity
'language' => 'string',
'category' => 'string',
'explicit' => 'boolean',
'author_name' => '?string',
'author_email' => '?string',
'owner_id' => 'integer',
'author' => '?string',
'owner_name' => '?string',
'owner_email' => '?string',
'type' => 'string',
@ -75,6 +68,8 @@ class Podcast extends Entity
'complete' => 'boolean',
'episode_description_footer' => '?string',
'custom_html_head' => '?string',
'created_by' => 'integer',
'updated_by' => 'integer',
];
public function setImage(\CodeIgniter\HTTP\Files\UploadedFile $image = null)
@ -139,33 +134,6 @@ class Podcast extends Entity
return $this->episodes;
}
/**
* Returns the podcast owner
*
* @return \App\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(\App\Entities\User $user)
{
$this->attributes['owner_id'] = $user->id;
return $this;
}
/**
* Returns all podcast contributors
*
@ -197,4 +165,18 @@ class Podcast extends Entity
return $converter->convertToHtml($this->attributes['description']);
}
public function setCreatedBy(\App\Entities\User $user)
{
$this->attributes['created_by'] = $user->id;
return $this;
}
public function setUpdatedBy(\App\Entities\User $user)
{
$this->attributes['updated_by'] = $user->id;
return $this;
}
}

View File

@ -64,7 +64,9 @@ function write_enclosure_tags($episode)
'title' => [$episode->title],
'artist' => [$episode->podcast->author],
'album' => [$episode->podcast->title],
'year' => [$episode->pub_date->format('Y')],
'year' => [
$episode->published_at ? $episode->published_at->format('Y') : '',
],
'genre' => ['Podcast'],
'comment' => [$episode->description],
'track_number' => [strval($episode->number)],

View File

@ -91,8 +91,8 @@ function get_rss_feed($podcast)
$itunes_namespace
);
$podcast->author_name &&
$channel->addChild('author', $podcast->author_name, $itunes_namespace);
$podcast->author &&
$channel->addChild('author', $podcast->author, $itunes_namespace);
$channel->addChild('link', $podcast->link);
if ($podcast->owner_name || $podcast->owner_email) {
@ -125,7 +125,10 @@ function get_rss_feed($podcast)
$enclosure->addAttribute('type', $enclosure_metadata['mime_type']);
$item->addChild('guid', $episode->guid);
$item->addChild('pubDate', $episode->pub_date->format(DATE_RFC1123));
$item->addChild(
'pubDate',
$episode->published_at->format(DATE_RFC1123)
);
$item->addChildWithCDATA('description', $episode->description_html);
$item->addChild(
'duration',
@ -145,18 +148,6 @@ function get_rss_feed($podcast)
$itunes_namespace
);
if ($episode->author_email || $episode->author_name) {
$item->addChild(
'author',
$episode->author_name
? $episode->author_email .
' (' .
$episode->author_name .
')'
: $episode->author_email
);
}
$item->addChild('episode', $episode->number, $itunes_namespace);
$episode->season_number &&
$item->addChild(

View File

@ -11,10 +11,10 @@ return [
'podcasts' => 'Podcasts',
'users' => 'Users',
'admin' => 'Home',
'my_podcasts' => 'My podcasts',
'podcast_list' => 'All podcasts',
'podcast_create' => 'New podcast',
'user_list' => 'All users',
'user_create' => 'New user',
'my-podcasts' => 'My podcasts',
'podcast-list' => 'All podcasts',
'podcast-create' => 'New podcast',
'user-list' => 'All users',
'user-create' => 'New user',
'go_to_website' => 'Go to website',
];

View File

@ -20,6 +20,9 @@ return [
'submit_add' => 'Add contributor',
'submit_edit' => 'Update role',
],
'roles' => [
'podcast_admin' => 'Podcast admin',
],
'messages' => [
'removeOwnerContributorError' => 'You can\'t remove the podcast owner!',
'removeContributorSuccess' =>

View File

@ -14,7 +14,7 @@ return [
'go_to_page' => 'Go to page',
'create' => 'Add an episode',
'form' => [
'file' => 'Audio file',
'enclosure' => 'Audio file',
'title' => 'Title',
'title_help' =>
'This episode title. It should contain a clear, concise name for your episode. Dont specify the episode number or season number here.',
@ -24,17 +24,19 @@ return [
'description' => 'Description',
'description_help' =>
'This is where you type the episode show notes. You may add rich text, links, images…',
'pub_date' => 'Publication date',
'pub_date_help' =>
'The date and time when this episode was released. It can be in the past or in the future.',
'image' => 'Image',
'image_help' =>
'This episode image. If an image is already in the audio file, you dont need to add one here. If you add no image to this episode, the podcast image will be used instead.',
'author_name' => 'Author name',
'author_email' => 'Author email',
'explicit' => 'Explicit',
'explicit_help' =>
'The episode parental advisory information for this episode.',
'published_at' => [
'label' => 'Publication date',
'date' => 'Publication date',
'time' => 'Publication time',
],
'published_at_help' =>
'The date and time when this episode was released. It can be in the past or in the future.',
'type' => [
'label' => 'Type',
'full' => 'Full',

View File

@ -10,6 +10,8 @@ return [
'info' => 'My account info',
'changePassword' => 'Change my password',
'messages' => [
'wrongPasswordError' =>
'You\'ve entered the wrong password, try again.',
'passwordChangeSuccess' => 'Password has been successfully changed!',
],
];

View File

@ -42,10 +42,9 @@ return [
'explicit' => 'Explicit',
'explicit_help' =>
'The podcast parental advisory information. Does it contain explicit content?',
'author_name' => 'Publisher',
'author_name_help' =>
'author' => 'Author',
'author_help' =>
'The group responsible for creating the show. Show author most often refers to the parent company or network of a podcast. This field is sometimes labeled as Author.',
'author_email' => 'Author email',
'owner_name' => 'Owner name',
'owner_name_help' =>
'The podcast owner contact name. For administrative use only. It will not be shown on podcasts platforms (such as Apple Podcasts) nor players (such as Podcast Addict) but it is visible in the public RSS feed.',

View File

@ -20,11 +20,13 @@ return [
'username' => 'Username',
'password' => 'Password',
'new_password' => 'New Password',
'repeat_password' => 'Repeat password',
'repeat_new_password' => 'Repeat new password',
'roles' => 'Roles',
'submit_create' => 'Create user',
'submit_edit' => 'Save',
'submit_password_change' => 'Change!',
],
'roles' => [
'superadmin' => 'Super admin',
],
'messages' => [
'createSuccess' =>

View File

@ -20,16 +20,16 @@ class EpisodeModel extends Model
'title',
'slug',
'enclosure_uri',
'pub_date',
'description',
'image_uri',
'explicit',
'number',
'season_number',
'author_name',
'author_email',
'type',
'block',
'published_at',
'created_by',
'updated_by',
];
protected $returnType = \App\Entities\Episode::class;
@ -42,13 +42,14 @@ class EpisodeModel extends Model
'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',
'number' => 'required|is_natural_no_zero',
'season_number' => 'required|is_natural_no_zero',
'type' => 'required',
'published_at' => 'valid_date|permit_empty',
'created_by' => 'required',
'updated_by' => 'required',
];
protected $validationMessages = [];

View File

@ -25,9 +25,7 @@ class PodcastModel extends Model
'language',
'category',
'explicit',
'author_name',
'author_email',
'owner_id',
'author',
'owner_name',
'owner_email',
'type',
@ -35,6 +33,8 @@ class PodcastModel extends Model
'block',
'complete',
'custom_html_head',
'created_by',
'updated_by',
];
protected $returnType = \App\Entities\Podcast::class;
@ -50,10 +50,10 @@ class PodcastModel extends Model
'image_uri' => 'required',
'language' => 'required',
'category' => 'required',
'author_email' => 'valid_email|permit_empty',
'owner_id' => 'required',
'owner_email' => 'required|valid_email',
'type' => 'required',
'created_by' => 'required',
'updated_by' => 'required',
];
protected $validationMessages = [];
@ -61,11 +61,6 @@ class PodcastModel extends Model
protected $afterUpdate = ['clearCache'];
protected $beforeDelete = ['clearCache'];
public function hello(array $data)
{
return $data;
}
/**
* Gets all the podcasts a given user is contributing to
*

View File

@ -9,16 +9,16 @@
</div>
<?= render_breadcrumb() ?>
<div class="relative ml-auto" data-toggle="dropdown">
<button type="button" class="inline-flex items-center px-2 py-1 outline-none focus:shadow-outline" id="myAccountDropdown" data-popper="button" aria-haspopup="true" aria-expanded="false">
<button type="button" class="inline-flex items-center px-2 py-1 outline-none focus:shadow-outline" id="my-accountDropdown" data-popper="button" aria-haspopup="true" aria-expanded="false">
Hey <?= user()->username ?>
<?= icon('caret-down', 'ml-2') ?>
</button>
<nav class="absolute z-10 flex-col hidden py-2 text-black whitespace-no-wrap bg-white border rounded shadow" aria-labelledby="myAccountDropdown" data-popper="menu" data-popper-placement="bottom-end">
<nav class="absolute z-10 flex-col hidden py-2 text-black whitespace-no-wrap bg-white border rounded shadow" aria-labelledby="my-accountDropdown" data-popper="menu" data-popper-placement="bottom-end">
<a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to(
'myAccount'
'my-account'
) ?>">My Account</a>
<a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to(
'myAccount_change-password'
'change-password'
) ?>">Change password</a>
<a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to(
'logout'

View File

@ -2,7 +2,7 @@
<img src="<?= $episode->image_url ?>" alt="<?= $episode->title ?>" class="object-cover w-32 h-32 rounded-l" />
<div class="flex flex-col flex-1 px-4 py-2">
<a href="<?= route_to(
'episode_view',
'episode-view',
$episode->podcast->id,
$episode->id
) ?>">
@ -17,17 +17,17 @@
</button>
<nav class="absolute z-10 flex-col hidden py-2 text-black whitespace-no-wrap bg-white border rounded shadow" aria-labelledby="moreDropdown" data-popper="menu" data-popper-placement="bottom-start" data-popper-offset-x="0" data-popper-offset-y="0" >
<a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to(
'episode_edit',
'episode-edit',
$episode->podcast->id,
$episode->id
) ?>"><?= lang('Episode.edit') ?></a>
<a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to(
'episode',
$episode->podcast->id,
$episode->podcast->name,
$episode->slug
) ?>"><?= lang('Episode.go_to_page') ?></a>
<a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to(
'episode_delete',
'episode-delete',
$episode->podcast->id,
$episode->id
) ?>"><?= lang('Episode.delete') ?></a>

View File

@ -2,7 +2,7 @@
<img alt="<?= $podcast->title ?>" src="<?= $podcast->image_url ?>" class="object-cover w-full h-40" />
<div class="p-2">
<a href="<?= route_to(
'podcast_view',
'podcast-view',
$podcast->id
) ?>" class="hover:underline">
<h2 class="font-semibold"><?= $podcast->title ?></h2>
@ -11,13 +11,13 @@
</div>
<footer class="flex items-center justify-end p-2">
<a class="inline-flex p-2 mr-2 text-teal-700 bg-teal-100 rounded-full shadow-xs hover:bg-teal-200" href="<?= route_to(
'podcast_edit',
'podcast-edit',
$podcast->id
) ?>" data-toggle="tooltip" data-placement="bottom" title="<?= lang(
'Podcast.edit'
) ?>"><?= icon('edit') ?></a>
<a class="inline-flex p-2 text-gray-700 bg-gray-100 rounded-full shadow-xs hover:bg-gray-200" href="<?= route_to(
'podcast_view',
'podcast-view',
$podcast->id
) ?>" data-toggle="tooltip" data-placement="bottom" title="<?= lang(
'Podcast.view'

View File

@ -3,9 +3,9 @@ $navigation = [
'dashboard' => ['icon' => 'dashboard', 'items' => ['admin']],
'podcasts' => [
'icon' => 'mic',
'items' => ['my_podcasts', 'podcast_list', 'podcast_create'],
'items' => ['my-podcasts', 'podcast-list', 'podcast-create'],
],
'users' => ['icon' => 'group', 'items' => ['user_list', 'user_create']],
'users' => ['icon' => 'group', 'items' => ['user-list', 'user-create']],
]; ?>
<nav class="<?= $class ?>">
@ -19,7 +19,7 @@ $navigation = [
<?php foreach ($data['items'] as $item): ?>
<?php $isActive = base_url(route_to($item)) == current_url(); ?>
<li>
<a class="block py-1 pl-10 pr-2 text-sm text-gray-600 outline-none hover:text-gray-900 focus:shadow-outline <?= $isActive
<a class="block py-1 pl-12 pr-2 text-sm text-gray-600 outline-none hover:text-gray-900 focus:shadow-outline <?= $isActive
? 'font-semibold text-gray-900'
: '' ?>" href="<?= route_to($item) ?>"><?= lang(
'AdminNavigation.' . $item

View File

@ -6,43 +6,32 @@
<?= $this->section('content') ?>
<form action="<?= route_to(
'contributor_add',
$podcast->id
) ?>" method="post" class="flex flex-col max-w-lg">
<?= csrf_field() ?>
<?= form_open(route_to('contributor-add', $podcast->id), [
'class' => 'flex flex-col max-w-sm',
]) ?>
<?= 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>
<?= form_label(lang('Contributor.form.user'), 'user') ?>
<?= form_dropdown('user', $userOptions, old('user'), [
'id' => 'user',
'class' => 'form-select mb-4',
'required' => 'required',
]) ?>
<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>
<?= form_label(lang('Contributor.form.role'), 'role') ?>
<?= form_dropdown('role', $roleOptions, old('role'), [
'id' => 'role',
'class' => 'form-select mb-4',
'required' => 'required',
]) ?>
<button type="submit" name="submit" class="self-end px-4 py-2 bg-gray-200"><?= lang(
'Contributor.form.submit_add'
) ?></button>
</form>
<?= form_button([
'content' => lang('Contributor.form.submit_add'),
'type' => 'submit',
'class' => 'self-end px-4 py-2 bg-gray-200',
]) ?>
<?= form_close() ?>
<?= $this->endSection() ?>

View File

@ -6,30 +6,25 @@
<?= $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 (
$contributorGroupId == $role->id
): ?> selected <?php endif; ?>>
<?= $role->name ?>
</option>
<?php endforeach; ?>
</select>
</div>
<?= form_open(route_to('contributor-edit', $podcast->id, $user->id), [
'class' => 'flex flex-col max-w-sm',
]) ?>
<?= csrf_field() ?>
<button type="submit" name="submit" class="self-end px-4 py-2 bg-gray-200"><?= lang(
'Contributor.form.submit_edit'
) ?></button>
<?= form_label(lang('Contributor.form.role'), 'role') ?>
<?= form_dropdown('role', $roleOptions, old('role', $contributorGroupId), [
'id' => 'role',
'class' => 'form-select mb-4',
'required' => 'required',
]) ?>
<?= form_button([
'content' => lang('Contributor.form.submit_edit'),
'type' => 'submit',
'class' => 'self-end px-4 py-2 bg-gray-200',
]) ?>
<?= form_close() ?>
</form>
<?= $this->endSection() ?>

View File

@ -3,7 +3,7 @@
<?= $this->section('title') ?>
<?= lang('Contributor.podcast_contributors') ?>
<a class="inline-flex items-center px-2 py-1 mb-2 ml-2 text-sm text-white bg-green-500 rounded shadow-xs outline-none hover:bg-green-600 focus:shadow-outline" href="<?= route_to(
'contributor_add',
'contributor-add',
$podcast->id
) ?>">
<?= icon('add', 'mr-2') ?>
@ -25,15 +25,17 @@
<?php foreach ($podcast->contributors as $contributor): ?>
<tr>
<td class="px-4 py-2 border"><?= $contributor->username ?></td>
<td class="px-4 py-2 border"><?= $contributor->podcast_role ?></td>
<td class="px-4 py-2 border"><?= lang(
'Contributor.roles.' . $contributor->podcast_role
) ?></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',
'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',
'contributor-remove',
$podcast->id,
$contributor->id
) ?>"><?= lang('Contributor.remove') ?></a>

View File

@ -7,122 +7,167 @@
<?= $this->section('content') ?>
<?= form_open_multipart(route_to('episode_create', $podcast->id), [
<?= form_open_multipart(route_to('episode-create', $podcast->id), [
'method' => 'post',
'class' => 'flex flex-col max-w-md',
]) ?>
<?= csrf_field() ?>
<div class="flex flex-col mb-4">
<label for="enclosure"><?= lang('Episode.form.file') ?></label>
<input type="file" class="form-input" id="enclosure" name="enclosure" required accept=".mp3,.m4a" />
<?= form_label(lang('Episode.form.enclosure'), 'enclosure') ?>
<?= form_input([
'id' => 'enclosure',
'name' => 'enclosure',
'class' => 'form-input mb-4',
'required' => 'required',
'type' => 'file',
'accept' => '.mp3,.m4a',
]) ?>
<?= form_label(lang('Episode.form.title'), 'title') ?>
<?= form_input([
'id' => 'title',
'name' => 'title',
'class' => 'form-input mb-4',
'value' => old('title'),
'required' => 'required',
'data-slugify' => 'title',
]) ?>
<?= form_label(lang('Episode.form.slug'), 'slug') ?>
<?= form_input([
'id' => 'slug',
'name' => 'slug',
'class' => 'form-input mb-4',
'value' => old('slug'),
'required' => 'required',
'data-slugify' => 'slug',
]) ?>
<div class="mb-4">
<?= form_label(lang('Episode.form.description'), 'description') ?>
<?= form_textarea(
[
'id' => 'description',
'name' => 'description',
'class' => 'form-textarea',
'required' => 'required',
],
old('description', '', false),
'data-editor="markdown"'
) ?>
</div>
<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" data-slugify="title" required value="<?= old(
'title'
) ?>" />
<?= form_fieldset('', ['class' => 'flex mb-4']) ?>
<legend><?= lang('Episode.form.published_at.label') ?></legend>
<div class="flex flex-col flex-1">
<?= form_label(lang('Episode.form.publication_date'), 'publication_date', [
'class' => 'sr-only',
]) ?>
<?= form_input([
'id' => 'publication_date',
'name' => 'publication_date',
'class' => 'form-input',
'value' => old('publication_date', date('Y-m-d')),
'type' => 'date',
]) ?>
</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" data-slugify="slug" required value="<?= old(
'slug'
) ?>" />
<div class="flex flex-col flex-1">
<?= form_label(lang('Episode.form.publication_time'), 'publication_time', [
'class' => 'sr-only',
]) ?>
<?= form_input([
'id' => 'publication_time',
'name' => 'publication_time',
'class' => 'form-input',
'value' => old('publication_time', date('H:i')),
'placeholder' => '--:--',
'type' => 'time',
]) ?>
</div>
<?= form_fieldset_close() ?>
<div class="flex flex-col mb-4">
<label for="description"><?= lang('Episode.form.description') ?></label>
<textarea class="hidden form-textarea" id="description" name="description" required data-editor="markdown"><?= 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="<?= old(
'pub_date'
) || date('Y-m-d') ?>" />
</div>
<?= form_label(lang('Episode.form.image'), 'image') ?>
<?= form_input([
'id' => 'image',
'name' => 'image',
'class' => 'form-input mb-4',
'type' => 'file',
'accept' => '.jpg,.jpeg,.png',
]) ?>
<div class="flex flex-col mb-4">
<label for="image"><?= lang('Episode.form.image') ?></label>
<input type="file" class="form-input" id="image" name="image" accept=".jpg,.jpeg,.png" />
</div>
<?= form_label(lang('Episode.form.season_number'), 'season_number') ?>
<?= form_input([
'id' => 'season_number',
'name' => 'season_number',
'class' => 'form-input mb-4',
'value' => old('season_number'),
'type' => 'number',
]) ?>
<div class="flex flex-col mb-4">
<label for="episode_number"><?= lang(
'Episode.form.episode_number'
) ?></label>
<input type="number" class="form-input" id="episode_number" name="episode_number" required value="<?= old(
'episode_number'
) ?>" />
</div>
<?= form_label(lang('Episode.form.episode_number'), 'episode_number') ?>
<?= form_input([
'id' => 'episode_number',
'name' => 'episode_number',
'class' => 'form-input mb-4',
'value' => old('episode_number'),
'required' => 'required',
'type' => 'number',
]) ?>
<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" value="<?= old(
'season_number'
) ?>" />
</div>
<label class="inline-flex items-center mb-4">
<?= form_checkbox(
['id' => 'explicit', 'name' => 'explicit', 'class' => 'form-checkbox'],
'yes',
old('explicit', false)
) ?>
<span class="ml-2"><?= lang('Episode.form.explicit') ?></span>
</label>
<div class="inline-flex items-center mb-4">
<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>
</div>
<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" 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" value="<?= old(
'author_email'
) ?>" />
</div>
<fieldset class="flex flex-col mb-4">
<?= form_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 <?php if (
!old('type') ||
old('type') == 'full'
): ?> checked <?php endif; ?> />
<?= form_radio(
['id' => 'full', 'name' => 'type', 'class' => 'form-radio'],
'full',
old('type') ? old('type') == 'full' : true
) ?>
<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 <?php if (
old('type') == 'trailer'
): ?> checked <?php endif; ?> />
<?= form_radio(
['id' => 'trailer', 'name' => 'type', 'class' => 'form-radio'],
'trailer',
old('type') ? old('type') == 'trailer' : false
) ?>
<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 <?php if (
old('type') == 'bonus'
): ?> checked <?php endif; ?> />
<?= form_radio(
['id' => 'bonus', 'name' => 'type', 'class' => 'form-radio'],
'bonus',
old('type') ? old('type') == 'bonus' : false
) ?>
<span class="ml-2"><?= lang('Episode.form.type.bonus') ?></span>
</label>
</fieldset>
<?= form_fieldset_close() ?>
<div class="inline-flex items-center mb-4">
<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>
<label class="inline-flex items-center mb-4">
<?= form_checkbox(
['id' => 'block', 'name' => 'block', 'class' => 'form-checkbox'],
'yes',
old('block', false)
) ?>
<span class="ml-2"><?= lang('Episode.form.block') ?></span>
</label>
<?= form_button([
'content' => lang('Episode.form.submit_create'),
'type' => 'submit',
'class' => 'self-end px-4 py-2 bg-gray-200',
]) ?>
<button type="submit" name="submit" class="self-end px-4 py-2 bg-gray-200"><?= lang(
'Episode.form.submit_create'
) ?></button>
<?= form_close() ?>

View File

@ -8,106 +8,173 @@
<?= $this->section('content') ?>
<?= form_open_multipart(
route_to('episode_edit', $episode->podcast->id, $episode->id),
[
'method' => 'post',
'class' => 'flex flex-col max-w-md',
]
route_to('episode-edit', $episode->podcast->id, $episode->id),
['method' => 'post', 'class' => 'flex flex-col max-w-md']
) ?>
<?= csrf_field() ?>
<div class="flex flex-col mb-4">
<label for="enclosure"><?= lang('Episode.form.file') ?></label>
<input type="file" class="form-input" id="enclosure" name="enclosure" accept=".mp3,.m4a" />
<?= form_label(lang('Episode.form.enclosure'), 'enclosure') ?>
<?= form_input([
'id' => 'enclosure',
'name' => 'enclosure',
'class' => 'form-input mb-4',
'type' => 'file',
'accept' => '.mp3,.m4a',
]) ?>
<?= form_label(lang('Episode.form.title'), 'title') ?>
<?= form_input([
'id' => 'title',
'name' => 'title',
'class' => 'form-input mb-4',
'value' => old('title', $episode->title),
'required' => 'required',
'data-slugify' => 'title',
]) ?>
<?= form_label(lang('Episode.form.slug'), 'slug') ?>
<?= form_input([
'id' => 'slug',
'name' => 'slug',
'class' => 'form-input mb-4',
'value' => old('slug', $episode->slug),
'required' => 'required',
'data-slugify' => 'slug',
]) ?>
<div class="mb-4">
<?= form_label(lang('Episode.form.description'), 'description') ?>
<?= form_textarea(
[
'id' => 'description',
'name' => 'description',
'class' => 'form-textarea',
'required' => 'required',
],
old('description', $episode->description, false),
'data-editor="markdown"'
) ?>
</div>
<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" data-slugify="title" value="<?= $episode->title ?>" required />
<?= form_fieldset('', ['class' => 'flex mb-4']) ?>
<legend><?= lang('Episode.form.published_at.label') ?></legend>
<div class="flex flex-col flex-1">
<?= form_label(lang('Episode.form.publication_date'), 'publication_date', [
'class' => 'sr-only',
]) ?>
<?= form_input([
'id' => 'publication_date',
'name' => 'publication_date',
'class' => 'form-input',
'value' => old(
'publication_date',
$episode->published_at
? $episode->published_at->format('Y-m-d')
: ''
),
'type' => 'date',
]) ?>
</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" data-slugify="slug" value="<?= $episode->slug ?>" required />
<div class="flex flex-col flex-1">
<?= form_label(lang('Episode.form.publication_time'), 'publication_time', [
'class' => 'sr-only',
]) ?>
<?= form_input([
'id' => 'publication_time',
'name' => 'publication_time',
'class' => 'form-input',
'value' => old(
'publication_time',
$episode->published_at ? $episode->published_at->format('H:i') : ''
),
'placeholder' => '--:--',
'type' => 'time',
]) ?>
</div>
<?= form_fieldset_close() ?>
<div class="flex flex-col mb-4">
<label for="description"><?= lang('Episode.form.description') ?></label>
<textarea class="form-textarea" id="description" name="description" required data-editor="markdown"><?= $episode->description ?></textarea>
</div>
<?= form_label(lang('Episode.form.image'), 'image') ?>
<img src="<?= $episode->image_url ?>" alt="<?= $episode->title ?>" class="object-cover w-32 h-32" />
<?= form_input([
'id' => 'image',
'name' => 'image',
'class' => 'form-input mb-4',
'type' => 'file',
'accept' => '.jpg,.jpeg,.png',
]) ?>
<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="<?= $episode->pub_date->format(
'Y-m-d'
) ?>" />
</div>
<?= form_label(lang('Episode.form.season_number'), 'season_number') ?>
<?= form_input([
'id' => 'season_number',
'name' => 'season_number',
'class' => 'form-input mb-4',
'value' => old('season_number', $episode->season_number),
'type' => 'number',
]) ?>
<div class="flex flex-col mb-4">
<label for="image"><?= lang('Episode.form.image') ?></label>
<input type="file" class="form-input" id="image" name="image" accept=".jpg,.jpeg,.png" />
</div>
<?= form_label(lang('Episode.form.episode_number'), 'episode_number') ?>
<?= form_input([
'id' => 'episode_number',
'name' => 'episode_number',
'class' => 'form-input mb-4',
'value' => old('episode_number', $episode->number),
'required' => 'required',
'type' => 'number',
]) ?>
<div class="flex flex-col mb-4">
<label for="episode_number"><?= lang(
'Episode.form.episode_number'
) ?></label>
<input type="number" class="form-input" id="episode_number" name="episode_number" value="<?= $episode->number ?>" required />
</div>
<label class="inline-flex items-center mb-4">
<?= form_checkbox(
['id' => 'explicit', 'name' => 'explicit', 'class' => 'form-checkbox'],
'yes',
old('explicit', $episode->explicit)
) ?>
<span class="ml-2"><?= lang('Episode.form.explicit') ?></span>
</label>
<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" value="<?= $episode->season_number ?>" />
</div>
<div class="inline-flex items-center mb-4">
<input type="checkbox" id="explicit" name="explicit" class="form-checkbox" <?= $episode->explicit
? 'checked'
: '' ?> />
<label for="explicit" class="pl-2"><?= lang(
'Episode.form.explicit'
) ?></label>
</div>
<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" value="<?= $episode->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" value="<?= $episode->author_email ?>" />
</div>
<fieldset class="flex flex-col mb-4">
<?= form_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
<?= $episode->type == 'full' ? 'checked' : '' ?>/>
<span class="ml-2"><?= lang('Episode.form.type.full') ?></span>
<?= form_radio(
['id' => 'full', 'name' => 'type', 'class' => 'form-radio'],
'full',
old('type') ? old('type') == 'full' : $episode->type == 'full'
) ?>
<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
<?= $episode->type == 'trailer' ? 'checked' : '' ?>/>
<span class="ml-2"><?= lang('Episode.form.type.trailer') ?></span>
<?= form_radio(
['id' => 'trailer', 'name' => 'type', 'class' => 'form-radio'],
'trailer',
old('type') ? old('type') == 'trailer' : $episode->type == 'trailer'
) ?>
<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
<?= $episode->type == 'bonus' ? 'checked' : '' ?> />
<span class="ml-2"><?= lang('Episode.form.type.bonus') ?></span>
<?= form_radio(
['id' => 'bonus', 'name' => 'type', 'class' => 'form-radio'],
'bonus',
old('type') ? old('type') == 'bonus' : $episode->type == 'bonus'
) ?>
<span class="ml-2"><?= lang('Episode.form.type.bonus') ?></span>
</label>
</fieldset>
<?= form_fieldset_close() ?>
<div class="inline-flex items-center mb-4">
<input type="checkbox" id="block" name="block" class="form-checkbox" <?= $episode->block
? 'checked'
: '' ?> />
<label for="block" class="pl-2"><?= lang('Episode.form.block') ?></label>
</div>
<label class="inline-flex items-center mb-4">
<?= form_checkbox(
['id' => 'block', 'name' => 'block', 'class' => 'form-checkbox'],
'yes',
old('block', $episode->block)
) ?>
<span class="ml-2"><?= lang('Episode.form.block') ?></span>
</label>
<?= form_button([
'content' => lang('Episode.form.submit_edit'),
'type' => 'submit',
'class' => 'self-end px-4 py-2 bg-gray-200',
]) ?>
<button type="submit" name="submit" class="self-end px-4 py-2 bg-gray-200"><?= lang(
'Episode.form.submit_edit'
) ?></button>
<?= form_close() ?>

View File

@ -4,7 +4,7 @@
<?= lang('Episode.all_podcast_episodes') ?> (<?= count($podcast->episodes) ?>)
<a class="inline-flex items-center px-2 py-1 mb-2 ml-2 text-sm text-white bg-green-500 rounded shadow-xs outline-none hover:bg-green-600 focus:shadow-outline" href="<?= route_to(
'episode_create',
'episode-create',
$podcast->id
) ?>">
<?= icon('add', 'mr-2') ?>

View File

@ -13,7 +13,7 @@
</audio>
<a class="inline-flex px-4 py-2 text-white bg-teal-700 hover:bg-teal-800" href="<?= route_to(
'episode_edit',
'episode-edit',
$episode->podcast->id,
$episode->id
) ?>"><?= lang('Episode.edit') ?></a>
@ -25,7 +25,7 @@
'Episode.go_to_page'
) ?></a>
<a href="<?= route_to(
'episode_delete',
'episode-delete',
$episode->podcast->id,
$episode->id
) ?>" class="inline-flex px-4 py-2 text-white bg-red-700 hover:bg-red-800"><?= lang(

View File

@ -7,28 +7,37 @@
<?= $this->section('content') ?>
<form action="<?= route_to(
'myAccount_changePassword'
) ?>" method="post" class="flex flex-col max-w-lg">
<?= csrf_field() ?>
<?= form_open(route_to('change-password'), [
'class' => 'flex flex-col max-w-sm',
]) ?>
<?= csrf_field() ?>
<input type="hidden" name="email" value="<?= user()->email ?>">
<?= form_label(lang('User.form.password'), 'password') ?>
<?= form_input([
'id' => 'password',
'name' => 'password',
'class' => 'form-input mb-4',
'required' => 'required',
'type' => 'password',
]) ?>
<label for="password"><?= lang('User.form.password') ?></label>
<input type="password" name="password" class="mb-4 form-input" id="password" autocomplete="off">
<?= form_label(lang('User.form.new_password'), 'new_password') ?>
<?= form_input([
'id' => 'new_password',
'name' => 'new_password',
'class' => 'form-input mb-4',
'required' => 'required',
'type' => 'password',
'autocomplete' => 'new-password',
]) ?>
<label for="new_password"><?= lang('User.form.new_password') ?></label>
<input type="password" name="new_password" class="mb-4 form-input" id="new_password" autocomplete="off">
<?= form_button([
'content' => lang('User.form.submit_password_change'),
'type' => 'submit',
'class' => 'self-end px-4 py-2 bg-gray-200',
]) ?>
<label for="pass_confirm"><?= lang(
'User.form.repeat_new_password'
) ?></label>
<input type="password" name="new_pass_confirm" class="mb-6 form-input" id="new_pass_confirm" autocomplete="off">
<button type="submit" class="px-4 py-2 ml-auto border">
<?= lang('User.form.submit_edit') ?>
</button>
</form>
<?= form_close() ?>
<?= $this->endSection()
?>

View File

@ -7,169 +7,186 @@
<?= $this->section('content') ?>
<?= form_open_multipart(route_to('podcast_create'), [
<?= form_open_multipart(route_to('podcast-create'), [
'method' => 'post',
'class' => 'flex flex-col max-w-md',
]) ?>
<?= csrf_field() ?>
<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" value="<?= old(
'title'
) ?>" required />
<?= form_label(lang('Podcast.form.title'), 'title') ?>
<?= form_input([
'id' => 'title',
'name' => 'title',
'class' => 'form-input mb-4',
'value' => old('title'),
'required' => 'required',
]) ?>
<?= form_label(lang('Podcast.form.name'), 'name') ?>
<?= form_input([
'id' => 'name',
'name' => 'name',
'class' => 'form-input mb-4',
'value' => old('name'),
'required' => 'required',
]) ?>
<div class="mb-4">
<?= form_label(lang('Podcast.form.description'), 'description') ?>
<?= form_textarea(
[
'id' => 'description',
'name' => 'description',
'class' => 'form-textarea',
'required' => 'required',
],
old('description', '', false),
'data-editor="markdown"'
) ?>
</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" 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 data-editor="markdown"><?= 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" data-editor="markdown"><?= old(
<div class="mb-4">
<?= form_label(
lang('Podcast.form.episode_description_footer'),
'episode_description_footer'
) ?></textarea>
) ?>
<?= form_textarea(
[
'id' => 'episode_description_footer',
'name' => 'episode_description_footer',
'class' => 'form-textarea',
],
old('episode_description_footer', '', false),
'data-editor="markdown"'
) ?>
</div>
<div class="flex flex-col mb-4">
<label for="image"><?= lang('Podcast.form.image') ?></label>
<input type="file" class="form-input" id="image" name="image" required />
</div>
<?= form_label(lang('Podcast.form.image'), 'image') ?>
<?= form_input([
'id' => 'image',
'name' => 'image',
'class' => 'form-input mb-4',
'required' => 'required',
'type' => 'file',
'accept' => '.jpg,.jpeg,.png',
]) ?>
<div class="flex flex-col mb-4">
<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 value="<?= $language->code ?>"
<?php if (
old('language') == $language->code
): ?> selected <?php endif; ?>
<?php if (
!old('language') &&
$language->code == $browserLang
): ?> selected <?php endif; ?>
>
<?= $language->native_name ?>
</option>
<?php endforeach; ?>
</select>
</div>
<?= form_label(lang('Podcast.form.language'), 'language') ?>
<?= form_dropdown('language', $languageOptions, old('language', $browserLang), [
'id' => 'language',
'class' => 'form-select mb-4',
'required' => 'required',
]) ?>
<div class="flex flex-col mb-4">
<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 ?>"
<?php if (
old('category') == $category->code
): ?> selected <?php endif; ?>
><?= lang('Podcast.category_options.' . $category->code) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<?= form_label(lang('Podcast.form.category'), 'category') ?>
<?= form_dropdown('category', $categoryOptions, old('category'), [
'id' => 'category',
'class' => 'form-select mb-4',
'required' => 'required',
]) ?>
<div class="inline-flex items-center mb-4">
<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>
</div>
<label class="inline-flex items-center mb-4">
<?= form_checkbox(
['id' => 'explicit', 'name' => 'explicit', 'class' => 'form-checkbox'],
'yes',
old('explicit', false)
) ?>
<span class="ml-2"><?= lang('Podcast.form.explicit') ?></span>
</label>
<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" value="<?= old(
'author_name'
) ?>" />
</div>
<?= form_label(lang('Podcast.form.author'), 'author') ?>
<?= form_input([
'id' => 'author',
'name' => 'author',
'class' => 'form-input mb-4',
'value' => old('author'),
]) ?>
<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" value="<?= old(
'author_email'
) ?>" />
</div>
<?= form_label(lang('Podcast.form.owner_name'), 'owner_name') ?>
<?= form_input([
'id' => 'owner_name',
'name' => 'owner_name',
'class' => 'form-input mb-4',
'value' => old('owner_name'),
]) ?>
<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" value="<?= old(
'owner_name'
) ?>" />
</div>
<?= form_label(lang('Podcast.form.owner_email'), 'owner_email') ?>
<?= form_input([
'id' => 'owner_email',
'name' => 'owner_email',
'class' => 'form-input mb-4',
'value' => old('owner_email'),
'type' => 'email',
'required' => 'required',
]) ?>
<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" value="<?= old(
'owner_email'
) ?>" required />
</div>
<fieldset class="flex flex-col mb-4">
<?= form_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 <?php if (
!old('type') ||
old('type') == 'episodic'
): ?> checked <?php endif; ?> />
<?= form_radio(
['id' => 'episodic', 'name' => 'type', 'class' => 'form-radio'],
'episodic',
old('type') ? old('type') == 'episodic' : true
) ?>
<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 <?php if (
old('type') == 'serial'
): ?> checked <?php endif; ?> />
<?= form_radio(
['id' => 'serial', 'name' => 'type', 'class' => 'form-radio'],
'serial',
old('type') ? old('type') == 'serial' : false
) ?>
<span class="ml-2"><?= lang('Podcast.form.type.serial') ?></span>
</label>
</fieldset>
<?= form_fieldset_close() ?>
<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" value="<?= old(
'copyright'
) ?>" />
<?= form_label(lang('Podcast.form.copyright'), 'copyright') ?>
<?= form_input([
'id' => 'copyright',
'name' => 'copyright',
'class' => 'form-input mb-4',
'value' => old('copyright'),
]) ?>
<label class="inline-flex items-center mb-4">
<?= form_checkbox(
['id' => 'block', 'name' => 'block', 'class' => 'form-checkbox'],
'yes',
old('block', false)
) ?>
<span class="ml-2"><?= lang('Podcast.form.block') ?></span>
</label>
<label class="inline-flex items-center mb-4">
<?= form_checkbox(
['id' => 'complete', 'name' => 'complete', 'class' => 'form-checkbox'],
'yes',
old('complete', false)
) ?>
<span class="ml-2"><?= lang('Podcast.form.complete') ?></span>
</label>
<div class="mb-4">
<?= form_label(lang('Podcast.form.custom_html_head'), 'custom_html_head') ?>
<?= form_textarea(
[
'id' => 'custom_html_head',
'name' => 'custom_html_head',
'class' => 'form-textarea',
],
old('custom_html_head', '', false),
'data-editor="html"'
) ?>
</div>
<div class="inline-flex items-center mb-4">
<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>
<?= form_button([
'content' => lang('Podcast.form.submit_create'),
'type' => 'submit',
'class' => 'self-end px-4 py-2 bg-gray-200',
]) ?>
<div class="inline-flex items-center mb-4">
<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>
</div>
<div class="flex flex-col mb-4">
<label for="custom_html_head"><?= esc(
lang('Podcast.form.custom_html_head')
) ?></label>
<textarea class="form-textarea" id="custom_html_head" name="custom_html_head" data-editor="html"><?= old(
'custom_html_head'
) ?></textarea>
</div>
<button type="submit" name="submit" class="self-end px-4 py-2 bg-gray-200"><?= lang(
'Podcast.form.submit_create'
) ?></button>
<?= form_close() ?>

View File

@ -7,139 +7,201 @@
<?= $this->section('content') ?>
<?= form_open_multipart(route_to('podcast_edit', $podcast->id), [
<?= form_open_multipart(route_to('podcast-edit', $podcast->id), [
'method' => 'post',
'class' => 'flex flex-col max-w-md',
]) ?>
<?= csrf_field() ?>
<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" value="<?= $podcast->title ?>" required />
<?= form_label(lang('Podcast.form.title'), 'title') ?>
<?= form_input([
'id' => 'title',
'name' => 'title',
'class' => 'form-input mb-4',
'value' => old('title', $podcast->title),
'required' => 'required',
]) ?>
<?= form_label(lang('Podcast.form.name'), 'name') ?>
<?= form_input([
'id' => 'name',
'name' => 'name',
'class' => 'form-input mb-4',
'value' => old('name', $podcast->name),
'required' => 'required',
]) ?>
<div class="mb-4">
<?= form_label(lang('Podcast.form.description'), 'description') ?>
<?= form_textarea(
[
'id' => 'description',
'name' => 'description',
'class' => 'form-textarea',
'required' => 'required',
],
old('description', $podcast->description, false),
'data-editor="markdown"'
) ?>
</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" value="<?= $podcast->name ?>" required />
<div class="mb-4">
<?= form_label(
lang('Podcast.form.episode_description_footer'),
'episode_description_footer'
) ?>
<?= form_textarea(
[
'id' => 'episode_description_footer',
'name' => 'episode_description_footer',
'class' => 'form-textarea',
],
old(
'episode_description_footer',
$podcast->episode_description_footer,
false
),
'data-editor="markdown"'
) ?>
</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 data-editor="markdown"><?= $podcast->description ?></textarea>
</div>
<?= form_label(lang('Podcast.form.image'), 'image') ?>
<img src="<?= $podcast->image_url ?>" alt="<?= $podcast->title ?>" class="object-cover w-32 h-32" />
<?= form_input([
'id' => 'image',
'name' => 'image',
'class' => 'form-input mb-4',
'type' => 'file',
'accept' => '.jpg,.jpeg,.png',
]) ?>
<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" data-editor="markdown"><?= $podcast->episode_description_footer ?></textarea>
</div>
<div class="flex flex-col mb-4">
<label for="image"><?= lang('Podcast.form.image') ?></label>
<input type="file" class="form-input" id="image" name="image" />
<img src="<?= $podcast->image_url ?>" alt="<?= $podcast->title ?>" class="object-cover w-32 h-32" />
</div>
<div class="flex flex-col mb-4">
<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 <?= $podcast->language == $language->code
? "selected='selected'"
: '' ?> value="<?= $language->code ?>">
<?= $language->native_name ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="flex flex-col mb-4">
<label for="category"><?= lang('Podcast.form.category') ?></label>
<select id="category" name="category" class="form-select" required>
<?php foreach ($categories as $category): ?>
<option <?= $podcast->category == $category->code
? "selected='selected'"
: '' ?> value="<?= $category->code ?>"><?= lang(
'Podcast.category_options.' . $category->code
<?= form_label(lang('Podcast.form.language'), 'language') ?>
<?= form_dropdown(
'language',
$languageOptions,
old('language', $podcast->language),
[
'id' => 'language',
'class' => 'form-select mb-4',
'required' => 'required',
]
) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="inline-flex items-center mb-4">
<input type="checkbox" id="explicit" name="explicit" class="form-checkbox" <?= $podcast->explicit
? 'checked'
: '' ?> />
<label for="explicit" class="pl-2"><?= lang(
'Podcast.form.explicit'
) ?></label>
</div>
<?= form_label(lang('Podcast.form.category'), 'category') ?>
<?= form_dropdown(
'category',
$categoryOptions,
old('category', $podcast->category),
[
'id' => 'category',
'class' => 'form-select mb-4',
'required' => 'required',
]
) ?>
<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" value="<?= $podcast->author_name ?>" />
</div>
<label class="inline-flex items-center mb-4">
<?= form_checkbox(
['id' => 'explicit', 'name' => 'explicit', 'class' => 'form-checkbox'],
'yes',
old('explicit', $podcast->explicit)
) ?>
<span class="ml-2"><?= lang('Podcast.form.explicit') ?></span>
</label>
<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" value="<?= $podcast->author_email ?>" />
</div>
<?= form_label(lang('Podcast.form.author'), 'author') ?>
<?= form_input([
'id' => 'author',
'name' => 'author',
'class' => 'form-input mb-4',
'value' => old('author', $podcast->author),
]) ?>
<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" value="<?= $podcast->owner_name ?>" />
</div>
<?= form_label(lang('Podcast.form.owner_name'), 'owner_name') ?>
<?= form_input([
'id' => 'owner_name',
'name' => 'owner_name',
'class' => 'form-input mb-4',
'value' => old('owner_name', $podcast->owner_name),
]) ?>
<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" value="<?= $podcast->owner_email ?>" required />
</div>
<?= form_label(lang('Podcast.form.owner_email'), 'owner_email') ?>
<?= form_input([
'id' => 'owner_email',
'name' => 'owner_email',
'class' => 'form-input mb-4',
'value' => old('owner_email', $podcast->owner_email),
'type' => 'email',
'required' => 'required',
]) ?>
<fieldset class="flex flex-col mb-4">
<?= form_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
<?= $podcast->type == 'episodic' ? 'checked' : '' ?> />
<span class="ml-2"><?= lang('Podcast.form.type.episodic') ?></span>
<?= form_radio(
['id' => 'episodic', 'name' => 'type', 'class' => 'form-radio'],
'episodic',
old('type')
? old('type') == 'episodic'
: $podcast->type == 'episodic'
) ?>
<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
<?= $podcast->type == 'serial' ? 'checked' : '' ?>/>
<span class="ml-2"><?= lang('Podcast.form.type.serial') ?></span>
<?= form_radio(
['id' => 'serial', 'name' => 'type', 'class' => 'form-radio'],
'serial',
old('type') ? old('type') == 'serial' : $podcast->type == 'serial'
) ?>
<span class="ml-2"><?= lang('Podcast.form.type.serial') ?></span>
</label>
</fieldset>
<?= form_fieldset_close() ?>
<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" value="<?= $podcast->copyright ?>" />
<?= form_label(lang('Podcast.form.copyright'), 'copyright') ?>
<?= form_input([
'id' => 'copyright',
'name' => 'copyright',
'class' => 'form-input mb-4',
'value' => old('copyright', $podcast->copyright),
]) ?>
<label class="inline-flex items-center mb-4">
<?= form_checkbox(
['id' => 'block', 'name' => 'block', 'class' => 'form-checkbox'],
'yes',
old('block', $podcast->block)
) ?>
<span class="ml-2"><?= lang('Podcast.form.block') ?></span>
</label>
<label class="inline-flex items-center mb-4">
<?= form_checkbox(
['id' => 'complete', 'name' => 'complete', 'class' => 'form-checkbox'],
'yes',
old('complete', $podcast->complete)
) ?>
<span class="ml-2"><?= lang('Podcast.form.complete') ?></span>
</label>
<div class="mb-4">
<?= form_label(lang('Podcast.form.custom_html_head'), 'custom_html_head') ?>
<?= form_textarea(
[
'id' => 'custom_html_head',
'name' => 'custom_html_head',
'class' => 'form-textarea',
],
old('custom_html_head', $podcast->custom_html_head, false),
'data-editor="html"'
) ?>
</div>
<div class="inline-flex items-center mb-4">
<input type="checkbox" id="block" name="block" class="form-checkbox"
<?= $podcast->block ? 'checked' : '' ?> />
<label for="block" class="pl-2"><?= lang('Podcast.form.block') ?></label>
</div>
<?= form_button([
'content' => lang('Podcast.form.submit_edit'),
'type' => 'submit',
'class' => 'self-end px-4 py-2 bg-gray-200',
]) ?>
<div class="inline-flex items-center mb-4">
<input type="checkbox" id="complete" name="complete" class="form-checkbox"
<?= $podcast->complete ? 'checked' : '' ?> />
<label for="complete" class="pl-2"><?= lang(
'Podcast.form.complete'
) ?></label>
</div>
<div class="flex flex-col mb-4">
<label for="custom_html_head"><?= esc(
lang('Podcast.form.custom_html_head')
) ?></label>
<textarea class="form-textarea" id="custom_html_head" name="custom_html_head" data-editor="html"><?= $podcast->custom_html_head ?></textarea>
</div>
<button type="submit" name="submit" class="self-end px-4 py-2 bg-gray-200"><?= lang(
'Podcast.form.submit_edit'
) ?></button>
<?= form_close() ?>

View File

@ -3,7 +3,7 @@
<?= $this->section('title') ?>
<?= lang('Podcast.all_podcasts') ?> (<?= count($podcasts) ?>)
<a class="inline-flex items-center px-2 py-1 mb-2 ml-4 text-sm text-white bg-green-500 rounded shadow-xs outline-none hover:bg-green-600 focus:shadow-outline" href="<?= route_to(
'podcast_create'
'podcast-create'
) ?>">
<?= icon('add', 'mr-2') ?>
<?= lang('Podcast.create') ?></a>

View File

@ -3,14 +3,14 @@
<?= $this->section('title') ?>
<?= $podcast->title ?>
<a class="inline-flex items-center px-2 py-1 mb-2 ml-4 text-sm text-white bg-teal-500 rounded shadow-xs outline-none hover:bg-teal-600 focus:shadow-outline" href="<?= route_to(
'podcast_edit',
'podcast-edit',
$podcast->id
) ?>">
<?= icon('edit', 'mr-2') ?>
<?= lang('Podcast.edit') ?>
</a>
<a class="inline-flex items-center px-2 py-1 mb-2 ml-2 text-sm text-white bg-green-500 rounded shadow-xs outline-none hover:bg-green-600 focus:shadow-outline" href="<?= route_to(
'episode_create',
'episode-create',
$podcast->id
) ?>">
<?= icon('add', 'mr-2') ?>
@ -20,7 +20,7 @@
<?= $this->section('content') ?>
<img class="w-64 mb-4" src="<?= $podcast->image_url ?>" alt="<?= $podcast->title ?>" />
<a class="inline-flex px-2 py-1 mb-2 text-white bg-yellow-700 hover:bg-yellow-800" href="<?= route_to(
'contributor_list',
'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(
@ -28,7 +28,7 @@
$podcast->name
) ?>"><?= lang('Podcast.go_to_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-delete',
$podcast->id
) ?>"><?= lang('Podcast.delete') ?></a>

View File

@ -7,31 +7,44 @@
<?= $this->section('content') ?>
<form action="<?= route_to(
'user_create'
) ?>" method="post" class="flex flex-col max-w-lg">
<?= csrf_field() ?>
<?= form_open(route_to('user-create'), [
'class' => 'flex flex-col max-w-sm',
]) ?>
<?= csrf_field() ?>
<label for="email"><?= lang('User.form.email') ?></label>
<input type="email" class="mb-4 form-input" name="email" id="email" value="<?= old(
'email'
) ?>">
<?= form_label(lang('User.form.email'), 'email') ?>
<?= form_input([
'id' => 'email',
'name' => 'email',
'class' => 'form-input mb-4',
'value' => old('email'),
'type' => 'email',
]) ?>
<label for="username"><?= lang('User.form.username') ?></label>
<input type="text" class="mb-4 form-input" name="username" id="username" value="<?= old(
'username'
) ?>">
<?= form_label(lang('User.form.username'), 'username') ?>
<?= form_input([
'id' => 'username',
'name' => 'username',
'class' => 'form-input mb-4',
'value' => old('username'),
]) ?>
<label for="password"><?= lang('User.form.password') ?></label>
<input type="password" name="password" class="mb-4 form-input" id="password" autocomplete="off">
<?= form_label(lang('User.form.password'), 'password') ?>
<?= form_input([
'id' => 'password',
'name' => 'password',
'class' => 'form-input mb-4',
'type' => 'password',
'autocomplete' => 'new-password',
]) ?>
<label for="pass_confirm"><?= lang('User.form.repeat_password') ?></label>
<input type="password" name="pass_confirm" class="mb-6 form-input" id="pass_confirm" autocomplete="off">
<?= form_button([
'content' => lang('User.form.submit_create'),
'type' => 'submit',
'class' => 'self-end px-4 py-2 bg-gray-200',
]) ?>
<button type="submit" class="px-4 py-2 ml-auto border">
<?= lang('User.form.submit_create') ?>
</button>
</form>
<?= form_close() ?>
<?= $this->endSection()
?>

View File

@ -7,27 +7,23 @@
<?= $this->section('content') ?>
<form action="<?= route_to(
'user_edit',
$user->id
) ?>" method="post" class="flex flex-col max-w-lg">
<?= csrf_field() ?>
<?= form_open(route_to('user-edit', $user->id), [
'class' => 'flex flex-col max-w-sm',
]) ?>
<?= csrf_field() ?>
<label for="roles"><?= lang('User.form.roles') ?></label>
<select id="roles" name="roles[]" autocomplete="off" class="mb-6 form-multiselect" multiple>
<?php foreach ($roles as $role): ?>
<option value="<?= $role->id ?>"
<?php if (
in_array($role->name, $user->roles)
): ?> selected <?php endif; ?>>
<?= $role->name ?>
</option>
<?php endforeach; ?>
</select>
<?= form_label(lang('User.form.roles'), 'roles') ?>
<?= form_multiselect('roles[]', $roleOptions, $user->roles, [
'id' => 'roles',
'class' => 'form-multiselect mb-4',
]) ?>
<button type="submit" class="px-4 py-2 ml-auto border">
<?= lang('User.form.submit_edit') ?>
</button>
</form>
<?= form_button([
'content' => lang('User.form.submit_edit'),
'type' => 'submit',
'class' => 'self-end px-4 py-2 bg-gray-200',
]) ?>
<?= form_close() ?>
<?= $this->endSection() ?>

View File

@ -25,7 +25,7 @@
<td class="px-4 py-2 border">
[<?= implode(', ', $user->roles) ?>]
<a class="inline-flex p-2 mr-2 text-teal-700 bg-teal-100 rounded-full shadow-xs hover:bg-teal-200" href="<?= route_to(
'user_edit',
'user-edit',
$user->id
) ?>" data-toggle="tooltip" data-placement="bottom"
title="<?= lang('User.edit_roles', [
@ -39,18 +39,18 @@
: 'No' ?></td>
<td class="px-4 py-2 border">
<a class="inline-flex px-2 py-1 mb-2 text-sm text-white bg-gray-700 hover:bg-gray-800" href="<?= route_to(
'user_force_pass_reset',
'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->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-delete',
$user->id
) ?>"><?= lang('User.delete') ?></a>
</td>

View File

@ -1,3 +1,4 @@
<?= helper('svg') ?>
<!DOCTYPE html>
<html lang="en">
@ -12,11 +13,15 @@
<body class="flex flex-col items-center justify-center min-h-screen mx-auto bg-gray-100">
<header class="mb-4">
<a href="<?= route_to('home') ?>" class="text-2xl"><?= $this->renderSection(
'title'
) ?></a>
<a href="<?= route_to('home') ?>" class="inline-flex items-center">
<?= svg(
'logo-castopod',
'text-3xl mr-2'
) ?><span class="text-xl">Castopod</span>
</a>
</header>
<main class="w-full max-w-md px-6 py-4 mx-auto bg-white rounded-lg shadow">
<h1 class="mb-2 text-2xl text-center"><?= $this->renderSection('title') ?></h1>
<?= view('_message_block') ?>
<?= $this->renderSection('content') ?>
</main>

View File

@ -1,3 +1,4 @@
<?= helper('form') ?>
<?= $this->extend($config->viewLayout) ?>
<?= $this->section('title') ?>
@ -7,19 +8,26 @@
<?= $this->section('content') ?>
<p class="mb-4"><?= lang('Auth.enterEmailForInstructions') ?></p>
<p class="mb-4 text-gray-600"><?= lang('Auth.enterEmailForInstructions') ?></p>
<form action="<?= route_to('forgot') ?>" method="post" class="flex flex-col">
<?= csrf_field() ?>
<?= form_open(route_to('forgot'), ['class' => 'flex flex-col']) ?>
<?= csrf_field() ?>
<label for="email"><?= lang('Auth.emailAddress') ?></label>
<input type="email" class="mb-6 form-input" name="email" placeholder="<?= lang(
'Auth.email'
) ?>">
<?= form_label(lang('Auth.emailAddress'), 'email') ?>
<?= form_input([
'id' => 'email',
'name' => 'email',
'class' => 'form-input mb-4',
'type' => 'email',
'required' => 'required',
]) ?>
<button type="submit" class="px-4 py-2 ml-auto border">
<?= lang('Auth.sendInstructions') ?>
</button>
</form>
<?= form_button([
'content' => lang('Auth.sendInstructions'),
'type' => 'submit',
'class' => 'px-4 py-2 ml-auto border',
]) ?>
<?= form_close() ?>
<?= $this->endSection() ?>

View File

@ -1,3 +1,4 @@
<?= helper('form') ?>
<?= $this->extend($config->viewLayout) ?>
<?= $this->section('title') ?>
@ -7,23 +8,33 @@
<?= $this->section('content') ?>
<form action="<?= route_to('login') ?>" method="post" class="flex flex-col">
<?= csrf_field() ?>
<?= form_open(route_to('login'), ['class' => 'flex flex-col']) ?>
<?= csrf_field() ?>
<label for="login"><?= lang('Auth.emailOrUsername') ?></label>
<input type="text" name="login" class="mb-4 form-input" placeholder="<?= lang(
'Auth.emailOrUsername'
) ?>">
<?= form_label(lang('Auth.emailOrUsername'), 'login') ?>
<?= form_input([
'id' => 'login',
'name' => 'login',
'class' => 'form-input mb-4',
'required' => 'required',
]) ?>
<label for="password"><?= lang('Auth.password') ?></label>
<input type="password" name="password" class="mb-6 form-input" placeholder="<?= lang(
'Auth.password'
) ?>">
<?= form_label(lang('Auth.password'), 'password') ?>
<?= form_input([
'id' => 'password',
'name' => 'password',
'class' => 'form-input mb-4',
'type' => 'password',
'required' => 'required',
]) ?>
<button type="submit" class="px-4 py-2 ml-auto border">
<?= lang('Auth.loginAction') ?>
</button>
</form>
<?= form_button([
'content' => lang('Auth.loginAction'),
'class' => 'px-4 py-2 ml-auto border',
'type' => 'submit',
]) ?>
<?= form_close() ?>
<?= $this->endSection() ?>

View File

@ -1,3 +1,4 @@
<?= helper('form') ?>
<?= $this->extend($config->viewLayout) ?>
<?= $this->section('title') ?>
@ -7,36 +8,49 @@
<?= $this->section('content') ?>
<form action="<?= route_to('register') ?>" method="post" class="flex flex-col">
<?= csrf_field() ?>
<?= form_open(route_to('register'), ['class' => 'flex flex-col']) ?>
<?= csrf_field() ?>
<label for="email"><?= lang('Auth.email') ?></label>
<input type="email" class="mb-4 form-input" name="email" aria-describedby="emailHelp" placeholder="<?= lang(
'Auth.email'
) ?>" value="<?= old('email') ?>">
<small id="emailHelp" class="mb-4">
<?= lang('Auth.weNeverShare') ?>
</small>
<?= form_label(lang('Auth.email'), 'email') ?>
<?= form_input([
'id' => 'email',
'name' => 'email',
'class' => 'form-input',
'value' => old('email'),
'type' => 'email',
'required' => 'required',
'aria-describedby' => 'emailHelp',
]) ?>
<small id="emailHelp" class="mb-4 text-gray-700">
<?= lang('Auth.weNeverShare') ?>
</small>
<label for="username"><?= lang('Auth.username') ?></label>
<input type="text" class="mb-4 form-input" name="username" placeholder="<?= lang(
'Auth.username'
) ?>" value="<?= old('username') ?>">
<?= form_label(lang('Auth.username'), 'username') ?>
<?= form_input([
'id' => 'username',
'name' => 'username',
'class' => 'form-input mb-4',
'value' => old('username'),
'required' => 'required',
]) ?>
<label for="password"><?= lang('Auth.password') ?></label>
<input type="password" name="password" class="mb-4 form-input" placeholder="<?= lang(
'Auth.password'
) ?>" autocomplete="off">
<?= form_label(lang('Auth.password'), 'password') ?>
<?= form_input([
'id' => 'password',
'name' => 'password',
'class' => 'form-input mb-4',
'type' => 'password',
'required' => 'required',
'autocomplete' => 'new-password',
]) ?>
<label for="pass_confirm"><?= lang('Auth.repeatPassword') ?></label>
<input type="password" name="pass_confirm" class="mb-6 form-input" placeholder="<?= lang(
'Auth.repeatPassword'
) ?>" autocomplete="off">
<?= form_button([
'content' => lang('Auth.register'),
'class' => 'px-4 py-2 ml-auto border',
'type' => 'submit',
]) ?>
<button type="submit" class="px-4 py-2 ml-auto border">
<?= lang('Auth.register') ?>
</button>
</form>
<?= form_close() ?>
<?= $this->endSection() ?>

View File

@ -1,3 +1,4 @@
<?= helper('form') ?>
<?= $this->extend($config->viewLayout) ?>
<?= $this->section('title') ?>
@ -9,30 +10,44 @@
<p class="mb-4"><?= lang('Auth.enterCodeEmailPassword') ?></p>
<form action="<?= route_to(
'reset-password'
) ?>" method="post" class="flex flex-col">
<?= csrf_field() ?>
<?= form_open(route_to('reset-password'), ['class' => 'flex flex-col']) ?>
<?= csrf_field() ?>
<label for="token"><?= lang('Auth.token') ?></label>
<input type="text" class="mb-4 form-input" name="token" placeholder="<?= lang(
'Auth.token'
) ?>" value="<?= old('token', $token ?? '') ?>">
<?= form_label(lang('Auth.token'), 'token') ?>
<?= form_input([
'id' => 'token',
'name' => 'token',
'class' => 'form-input mb-4',
'value' => old('token', $token ?? ''),
'required' => 'required',
]) ?>
<label for="email"><?= lang('Auth.email') ?></label>
<input type="email" class="mb-4 form-input" name="email" placeholder="<?= lang(
'Auth.email'
) ?>" value="<?= old('email') ?>">
<?= form_label(lang('Auth.email'), 'email') ?>
<?= form_input([
'id' => 'email',
'name' => 'email',
'class' => 'form-input mb-4',
'value' => old('email'),
'required' => 'required',
'type' => 'email',
]) ?>
<label for="password"><?= lang('Auth.newPassword') ?></label>
<input type="password" class="mb-4 form-input" name="password">
<?= form_label(lang('Auth.newPassword'), 'password') ?>
<?= form_input([
'id' => 'password',
'name' => 'password',
'class' => 'form-input mb-4',
'type' => 'password',
'required' => 'required',
'autocomplete' => 'new-password',
]) ?>
<label for="pass_confirm"><?= lang('Auth.newPasswordRepeat') ?></label>
<input type="password" class="mb-6 form-input" name="pass_confirm">
<?= form_button([
'content' => lang('Auth.resetPassword'),
'class' => 'px-4 py-2 ml-auto border',
'type' => 'submit',
]) ?>
<button type="submit" class="px-4 py-2 ml-auto border">
<?= lang('Auth.resetPassword') ?>
</button>
</form>
<?= form_close() ?>
<?= $this->endSection() ?>

View File

@ -2,9 +2,10 @@
<?= $this->section('content') ?>
<?= form_open(route_to('install_generate_env'), [
<?= form_open(route_to('generate-env'), [
'class' => 'flex flex-col max-w-sm mx-auto',
]) ?>
<?= csrf_field() ?>
<?= form_fieldset('', ['class' => 'flex flex-col mb-6']) ?>
<legend class="mb-4 text-xl"><?= lang(
@ -16,6 +17,7 @@
'name' => 'hostname',
'class' => 'form-input mb-4',
'value' => config('App')->baseURL,
'required' => 'required',
]) ?>
<?= form_label(lang('Install.form.admin_gateway'), 'admin_gateway') ?>
@ -24,6 +26,7 @@
'name' => 'admin_gateway',
'class' => 'form-input mb-4',
'value' => config('App')->adminGateway,
'required' => 'required',
]) ?>
<?= form_label(lang('Install.form.auth_gateway'), 'auth_gateway') ?>
@ -32,6 +35,7 @@
'name' => 'auth_gateway',
'class' => 'form-input',
'value' => config('App')->authGateway,
'required' => 'required',
]) ?>
<?= form_fieldset_close() ?>
@ -43,6 +47,7 @@
'name' => 'db_hostname',
'class' => 'form-input mb-4',
'value' => config('Database')->default['hostname'],
'required' => 'required',
]) ?>
<?= form_label(lang('Install.form.db_name'), 'db_name') ?>
@ -51,6 +56,7 @@
'name' => 'db_name',
'class' => 'form-input mb-4',
'value' => config('Database')->default['database'],
'required' => 'required',
]) ?>
<?= form_label(lang('Install.form.db_username'), 'db_username') ?>
@ -59,6 +65,7 @@
'name' => 'db_username',
'class' => 'form-input mb-4',
'value' => config('Database')->default['username'],
'required' => 'required',
]) ?>
<?= form_label(lang('Install.form.db_password'), 'db_password') ?>
@ -67,6 +74,7 @@
'name' => 'db_password',
'class' => 'form-input mb-4',
'value' => config('Database')->default['password'],
'required' => 'required',
]) ?>
<?= form_label(lang('Install.form.db_prefix'), 'db_prefix') ?>

View File

@ -2,9 +2,10 @@
<?= $this->section('content') ?>
<?= form_open(route_to('install_create_superadmin'), [
<?= form_open(route_to('create-superadmin'), [
'class' => 'flex flex-col max-w-sm mx-auto',
]) ?>
<?= csrf_field() ?>
<?= form_fieldset('', ['class' => 'flex flex-col mb-6']) ?>
<legend class="mb-4 text-xl"><?= lang(
@ -16,6 +17,8 @@
'name' => 'email',
'class' => 'form-input mb-4',
'type' => 'email',
'required' => 'required',
'value' => old('email'),
]) ?>
<?= form_label(lang('Install.form.username'), 'username') ?>
@ -23,14 +26,18 @@
'id' => 'username',
'name' => 'username',
'class' => 'form-input mb-4',
'required' => 'required',
'value' => old('username'),
]) ?>
<?= form_label(lang('Install.form.password'), 'password') ?>
<?= form_input([
'id' => 'password',
'name' => 'password',
'class' => 'form-input',
'class' => 'form-input mb-4',
'type' => 'password',
'required' => 'required',
'autocomplete' => 'new-password',
]) ?>
<?= form_fieldset_close() ?>