diff --git a/app/Config/App.php b/app/Config/App.php index f05de3d87ef176bd032040f91b56e373896fa07e..5dd608b357430f05152a5310dee629129d784cf7 100644 --- a/app/Config/App.php +++ b/app/Config/App.php @@ -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'; } diff --git a/app/Config/Routes.php b/app/Config/Routes.php index dc8d3ce95b5e9214237f2d5d2bb96aeba87ad005..43df8b3b09470ba5a489146b08a8271fc7c185b4 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -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', - ]); - $routes->get('edit', 'Podcast::edit/$1', [ - 'as' => 'podcast_edit', - 'filter' => 'permission:podcasts-edit,podcast-edit', + // Podcasts + $routes->group('podcasts', function ($routes) { + $routes->get('/', 'Podcast::list', [ + 'as' => 'podcast-list', ]); - $routes->post('edit', 'Podcast::attemptEdit/$1', [ - 'filter' => 'permission:podcasts-edit,podcast-edit', + $routes->get('new', 'Podcast::create', [ + 'as' => 'podcast-create', + 'filter' => 'permission:podcasts-create', ]); - $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->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->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)/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'); +}); /** * -------------------------------------------------------------------- diff --git a/app/Controllers/Admin/Contributor.php b/app/Controllers/Admin/Contributor.php index def6ab688a5ce95980d7e4db2cf34a832b8cda35..c892dc5c14863e001160e876765b2fbbe250454d 100644 --- a/app/Controllers/Admin/Contributor.php +++ b/app/Controllers/Admin/Contributor.php @@ -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() diff --git a/app/Controllers/Admin/Episode.php b/app/Controllers/Admin/Episode.php index d1bc612cbbf53650d9d67d8d5c83486b8993a4fa..32ca7f3a6c6af8eee0f163e96d1ed3ee36d0f9fe 100644 --- a/app/Controllers/Admin/Episode.php +++ b/app/Controllers/Admin/Episode.php @@ -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]); } } diff --git a/app/Controllers/Admin/Myaccount.php b/app/Controllers/Admin/Myaccount.php index fa7e7b4d395f7c7640cb5fd69393b3cd7f02a2eb..b6d19117aa7e0f3d8977943605ee4fa235a36f35 100644 --- a/app/Controllers/Admin/Myaccount.php +++ b/app/Controllers/Admin/Myaccount.php @@ -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')); } } diff --git a/app/Controllers/Admin/Podcast.php b/app/Controllers/Admin/Podcast.php index 3b18f44bf0a4b472480d9599feacf98f3ed2c40c..2f9dcef13d201baa7d73cbef02dc446a2eff2a3d 100644 --- a/app/Controllers/Admin/Podcast.php +++ b/app/Controllers/Admin/Podcast.php @@ -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'); } } diff --git a/app/Controllers/Admin/User.php b/app/Controllers/Admin/User.php index 52528c6734fadafaec5c2b5965048999217b8ffc..63874156ca3f959c4f8dcf675108096d8e069ed9 100644 --- a/app/Controllers/Admin/User.php +++ b/app/Controllers/Admin/User.php @@ -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', [ diff --git a/app/Controllers/Auth.php b/app/Controllers/Auth.php new file mode 100644 index 0000000000000000000000000000000000000000..aaac73ccc03e9e40458d7a8a67bb3d9f15a5fc65 --- /dev/null +++ b/app/Controllers/Auth.php @@ -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')); + } +} diff --git a/app/Database/Migrations/2020-05-30-101000_add_languages.php b/app/Database/Migrations/2020-05-30-101000_add_languages.php index 95c817ff3c00aabf729f47f97d4b68c9efdcbcc8..ae11bdf705e7c317854728336cfda6303056b8a6 100644 --- a/app/Database/Migrations/2020-05-30-101000_add_languages.php +++ b/app/Database/Migrations/2020-05-30-101000_add_languages.php @@ -30,7 +30,6 @@ class AddLanguages extends Migration ], 'native_name' => [ 'type' => 'VARCHAR', - 'comment' => 'Native language name.', 'constraint' => 191, ], ]); diff --git a/app/Database/Migrations/2020-05-30-101500_add_podcasts.php b/app/Database/Migrations/2020-05-30-101500_add_podcasts.php index 373345dc36021cd72f9ce6a8c03e3f5fd94bde36..368699c11ad45e7dcb625d2b05cf24bad319cc5f 100644 --- a/app/Database/Migrations/2020-05-30-101500_add_podcasts.php +++ b/app/Database/Migrations/2020-05-30-101500_add_podcasts.php @@ -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. It’s 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 aren’t available in some Apple Podcasts territories. False: If you specify false, indicating that your podcast doesn’t 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 isn’t 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 isn’t 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'); } diff --git a/app/Database/Migrations/2020-06-05-170000_add_episodes.php b/app/Database/Migrations/2020-06-05-170000_add_episodes.php index b29f0583ac4781f96a41c2471eb1cbc08099d4cd..23bd03ff3725aa305035af61b14bc175c6521491 100644 --- a/app/Database/Migrations/2020-06-05-170000_add_episodes.php +++ b/app/Database/Migrations/2020-06-05-170000_add_episodes.php @@ -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. Don’t 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 aren’t 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 doesn’t 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'); } diff --git a/app/Database/Migrations/2020-06-05-190000_add_platforms.php b/app/Database/Migrations/2020-06-05-190000_add_platforms.php index 5c3b53af93ea778b9d12690148f0e7c3ddc8fa60..c65929d09812fb7ee1d41ec6e8dfbc78c3306b9b 100644 --- a/app/Database/Migrations/2020-06-05-190000_add_platforms.php +++ b/app/Database/Migrations/2020-06-05-190000_add_platforms.php @@ -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', diff --git a/app/Database/Migrations/2020-06-08-160000_add_platform_links.php b/app/Database/Migrations/2020-06-08-160000_add_platform_links.php index 214a27c677e653831acc121005f8289868567324..bc44741f62ff21e32ec090abd1b99bfe6c29d783 100644 --- a/app/Database/Migrations/2020-06-08-160000_add_platform_links.php +++ b/app/Database/Migrations/2020-06-08-160000_add_platform_links.php @@ -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', diff --git a/app/Database/Migrations/2020-06-08-210000_add_analytics_episodes_by_country.php b/app/Database/Migrations/2020-06-08-210000_add_analytics_episodes_by_country.php index 18141d0d0034ce2d1baa7fae933b9dcc8f6b1158..c4f6218a54fbb5cf6156c739fb7bc06e611fda8b 100644 --- a/app/Database/Migrations/2020-06-08-210000_add_analytics_episodes_by_country.php +++ b/app/Database/Migrations/2020-06-08-210000_add_analytics_episodes_by_country.php @@ -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); diff --git a/app/Database/Migrations/2020-06-08-210000_add_analytics_episodes_by_player.php b/app/Database/Migrations/2020-06-08-210000_add_analytics_episodes_by_player.php index 7e6fd2bbaf68003ec64dbf3ed50d6c4b16130de0..3a1e257af60fd0f47cb6e814ca0bd3b272dca3e0 100644 --- a/app/Database/Migrations/2020-06-08-210000_add_analytics_episodes_by_player.php +++ b/app/Database/Migrations/2020-06-08-210000_add_analytics_episodes_by_player.php @@ -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); diff --git a/app/Database/Migrations/2020-06-08-210000_add_analytics_podcasts_by_country.php b/app/Database/Migrations/2020-06-08-210000_add_analytics_podcasts_by_country.php index c4d1ec373845b7b3035f6e85f3aa714d989ec950..6545a7a11bb1b81dd2b842323ad90357abdc6119 100644 --- a/app/Database/Migrations/2020-06-08-210000_add_analytics_podcasts_by_country.php +++ b/app/Database/Migrations/2020-06-08-210000_add_analytics_podcasts_by_country.php @@ -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); diff --git a/app/Database/Migrations/2020-06-08-210000_add_analytics_podcasts_by_player.php b/app/Database/Migrations/2020-06-08-210000_add_analytics_podcasts_by_player.php index 7aa84b624f7404a5234e4318e5f3490fb1e81a17..3a13f65fd59bba1ca950c73cc852e9d3d70b8dc0 100644 --- a/app/Database/Migrations/2020-06-08-210000_add_analytics_podcasts_by_player.php +++ b/app/Database/Migrations/2020-06-08-210000_add_analytics_podcasts_by_player.php @@ -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); diff --git a/app/Database/Migrations/2020-06-08-210000_add_analytics_unknown_useragents.php b/app/Database/Migrations/2020-06-08-210000_add_analytics_unknown_useragents.php index 9a65045f760ab4234c0c518ce6820a96f879b6ad..9327e2abdc46cb23d47281b37dae3614695890a3 100644 --- a/app/Database/Migrations/2020-06-08-210000_add_analytics_unknown_useragents.php +++ b/app/Database/Migrations/2020-06-08-210000_add_analytics_unknown_useragents.php @@ -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); diff --git a/app/Database/Migrations/2020-06-08-210000_add_analytics_website_by_browser.php b/app/Database/Migrations/2020-06-08-210000_add_analytics_website_by_browser.php index d86c3395e1f9dae4210721514488348fe0658c7c..6e4942d43317c4f7a70577ae4ab345fb10401cf8 100644 --- a/app/Database/Migrations/2020-06-08-210000_add_analytics_website_by_browser.php +++ b/app/Database/Migrations/2020-06-08-210000_add_analytics_website_by_browser.php @@ -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); diff --git a/app/Database/Migrations/2020-06-08-210000_add_analytics_website_by_country.php b/app/Database/Migrations/2020-06-08-210000_add_analytics_website_by_country.php index c0e5b73183ec90719dfbaec5669a7bc9818feb62..7f8b1415c0fd0f0afb1242249ae2b1b50790c074 100644 --- a/app/Database/Migrations/2020-06-08-210000_add_analytics_website_by_country.php +++ b/app/Database/Migrations/2020-06-08-210000_add_analytics_website_by_country.php @@ -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); diff --git a/app/Database/Migrations/2020-06-08-210000_add_analytics_website_by_referer.php b/app/Database/Migrations/2020-06-08-210000_add_analytics_website_by_referer.php index fa0ec5ea0218b7c47dce0f271cd4b7bbea7bdad8..28808f273e40adf36d617f2ead748070083df75f 100644 --- a/app/Database/Migrations/2020-06-08-210000_add_analytics_website_by_referer.php +++ b/app/Database/Migrations/2020-06-08-210000_add_analytics_website_by_referer.php @@ -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); diff --git a/app/Entities/Episode.php b/app/Entities/Episode.php index f60a5244808b8c061059dce8963ec41e0ded92f1..45f6bae67560d11c925e2abe1f5d940188bb6c5c 100644 --- a/app/Entities/Episode.php +++ b/app/Entities/Episode.php @@ -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; + } } diff --git a/app/Entities/Podcast.php b/app/Entities/Podcast.php index cb4040fe33e4a13e78528c671ec5424558d235fb..c139ef028e1147c486639ac02b93cfa52dbc716d 100644 --- a/app/Entities/Podcast.php +++ b/app/Entities/Podcast.php @@ -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; + } } diff --git a/app/Helpers/id3_helper.php b/app/Helpers/id3_helper.php index a80eaaf55ae90080ca5f6d8397ceadff021a5164..79335050290be26225aa618fae7b32f07a733132 100644 --- a/app/Helpers/id3_helper.php +++ b/app/Helpers/id3_helper.php @@ -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)], diff --git a/app/Helpers/rss_helper.php b/app/Helpers/rss_helper.php index ba665aab396e4cc2f3744ed8fb6294b25d003143..690049f2e492eb6767fa7de487a4a87747d141c7 100644 --- a/app/Helpers/rss_helper.php +++ b/app/Helpers/rss_helper.php @@ -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( diff --git a/app/Language/en/AdminNavigation.php b/app/Language/en/AdminNavigation.php index 255e1b5ed09c999a166a3e835b9b6e636e65535b..4f22158013794bec3af8461d4b2d1d2f64d7eac7 100644 --- a/app/Language/en/AdminNavigation.php +++ b/app/Language/en/AdminNavigation.php @@ -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', ]; diff --git a/app/Language/en/Contributor.php b/app/Language/en/Contributor.php index 25fed243667fe145cd0d15026906c0ccf67725e8..46b95f04164116625447c356e597feed7bd1802a 100644 --- a/app/Language/en/Contributor.php +++ b/app/Language/en/Contributor.php @@ -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' => diff --git a/app/Language/en/Episode.php b/app/Language/en/Episode.php index 2c4b3adf9b2669d9641caadfbb80cfb20d50560e..ae5e3db1a8e72cd239bf38bfc51409cd29b44af7 100644 --- a/app/Language/en/Episode.php +++ b/app/Language/en/Episode.php @@ -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. Don’t 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 don’t 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', diff --git a/app/Language/en/MyAccount.php b/app/Language/en/MyAccount.php index b675cc177005156f91041bb8e2fa59407929517d..b9b001d8d0117cdea8a79fd21a4dd290cc8886b0 100644 --- a/app/Language/en/MyAccount.php +++ b/app/Language/en/MyAccount.php @@ -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!', ], ]; diff --git a/app/Language/en/Podcast.php b/app/Language/en/Podcast.php index edf686cd2d00b377881613272fa49300edbd31eb..03bae0199cbe74caf58686e67c7a55f1cba1924e 100644 --- a/app/Language/en/Podcast.php +++ b/app/Language/en/Podcast.php @@ -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.', diff --git a/app/Language/en/User.php b/app/Language/en/User.php index 0e742c2a3200b228dd476a1e75128ae02ab4618d..2e221aaf300058a47fa07b518fe64f945d2fc9c6 100644 --- a/app/Language/en/User.php +++ b/app/Language/en/User.php @@ -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' => diff --git a/app/Models/EpisodeModel.php b/app/Models/EpisodeModel.php index 9514abec0f5c8224fd96f83d97b4dd39bd342169..cc68834c7bb35eb58a51df66fa11795ce5476b9e 100644 --- a/app/Models/EpisodeModel.php +++ b/app/Models/EpisodeModel.php @@ -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 = []; diff --git a/app/Models/PodcastModel.php b/app/Models/PodcastModel.php index acf34709d0ba67f43ed7db905f3559325f59df9a..01bbdbb4dc8d8ffcea0a47079cb66e7b7a18cf9c 100644 --- a/app/Models/PodcastModel.php +++ b/app/Models/PodcastModel.php @@ -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 * diff --git a/app/Views/admin/_header.php b/app/Views/admin/_header.php index 6756660b62498dcfc41e97397dca3cd631c2ae4f..6f3605a1144f6bb14c77c7f086bf9555c92551df 100644 --- a/app/Views/admin/_header.php +++ b/app/Views/admin/_header.php @@ -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' diff --git a/app/Views/admin/_partials/_episode-card.php b/app/Views/admin/_partials/_episode-card.php index af1b46de638f237cf9e7fa246414154edcfcba84..353674de7323ffc978eec74c35287bee2dcfa4d0 100644 --- a/app/Views/admin/_partials/_episode-card.php +++ b/app/Views/admin/_partials/_episode-card.php @@ -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> diff --git a/app/Views/admin/_partials/_podcast-card.php b/app/Views/admin/_partials/_podcast-card.php index a56c7fe6a619971e8b4799adc71e3ebb6a0b2187..0db3051be5cf64aa4297425a1cba65643fca38fa 100644 --- a/app/Views/admin/_partials/_podcast-card.php +++ b/app/Views/admin/_partials/_podcast-card.php @@ -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' diff --git a/app/Views/admin/_sidenav.php b/app/Views/admin/_sidenav.php index dad0054913c6f52efe7457640ef60a8738bc647b..5cec5d8b749e071d3519178f16580de723591a95 100644 --- a/app/Views/admin/_sidenav.php +++ b/app/Views/admin/_sidenav.php @@ -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 diff --git a/app/Views/admin/contributor/add.php b/app/Views/admin/contributor/add.php index 69bbe71a3957386f43d8df5f3755d739d854ac73..2f69a13aca6b3ad22e68b5310e028a06dd4793e0 100644 --- a/app/Views/admin/contributor/add.php +++ b/app/Views/admin/contributor/add.php @@ -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> - - <div class="flex flex-col mb-4"> - <label for="role"><?= lang('Contributor.form.role') ?></label> - <select id="role" name="role" autocomplete="off" class="form-select" required> - <?php foreach ($roles as $role): ?> - <option value="<?= $role->id ?>" - <?php if ( - old('role') == $role->id - ): ?> selected <?php endif; ?>> - <?= $role->name ?> - </option> - <?php endforeach; ?> - </select> - </div> - - <button type="submit" name="submit" class="self-end px-4 py-2 bg-gray-200"><?= lang( - 'Contributor.form.submit_add' - ) ?></button> -</form> +<?= form_label(lang('Contributor.form.user'), 'user') ?> +<?= form_dropdown('user', $userOptions, old('user'), [ + 'id' => 'user', + 'class' => 'form-select mb-4', + 'required' => 'required', +]) ?> + +<?= form_label(lang('Contributor.form.role'), 'role') ?> +<?= form_dropdown('role', $roleOptions, old('role'), [ + 'id' => 'role', + 'class' => 'form-select mb-4', + 'required' => 'required', +]) ?> + +<?= form_button([ + 'content' => lang('Contributor.form.submit_add'), + 'type' => 'submit', + 'class' => 'self-end px-4 py-2 bg-gray-200', +]) ?> + +<?= form_close() ?> <?= $this->endSection() ?> diff --git a/app/Views/admin/contributor/edit.php b/app/Views/admin/contributor/edit.php index 3329cf85b7b5a0183daffc69bb564a8b68323108..3a2c73addaaca85a8e4fbe2c6d85cc777bd1f47e 100644 --- a/app/Views/admin/contributor/edit.php +++ b/app/Views/admin/contributor/edit.php @@ -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> - - <button type="submit" name="submit" class="self-end px-4 py-2 bg-gray-200"><?= lang( - 'Contributor.form.submit_edit' - ) ?></button> - -</form> + +<?= form_open(route_to('contributor-edit', $podcast->id, $user->id), [ + 'class' => 'flex flex-col max-w-sm', +]) ?> +<?= csrf_field() ?> + +<?= 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() ?> + <?= $this->endSection() ?> diff --git a/app/Views/admin/contributor/list.php b/app/Views/admin/contributor/list.php index b0e7eb41a074f809d7c75e7073a3223f72f518be..e3c5e9874f16ada7c5bf85cb254367e834e58480 100644 --- a/app/Views/admin/contributor/list.php +++ b/app/Views/admin/contributor/list.php @@ -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> diff --git a/app/Views/admin/episode/create.php b/app/Views/admin/episode/create.php index 1651f833b26b2447a3a1d99e8ed2b95d1847b658..c19a0973496765819a81e1f7cc6143acd2339518 100644 --- a/app/Views/admin/episode/create.php +++ b/app/Views/admin/episode/create.php @@ -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" /> -</div> +<?= 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', +]) ?> -<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' - ) ?>" /> -</div> +<?= 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', +]) ?> -<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> +<?= 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="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 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="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') ?>" /> +<?= 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="image"><?= lang('Episode.form.image') ?></label> - <input type="file" class="form-input" id="image" name="image" accept=".jpg,.jpeg,.png" /> +<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="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> -<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> +<?= 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="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> +<?= 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="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('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="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> +<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> -<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> - -<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> +<?= form_fieldset_close() ?> + +<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() ?> diff --git a/app/Views/admin/episode/edit.php b/app/Views/admin/episode/edit.php index 4128b90672847754b03786ea4215dda941bbba97..7076114fe32c5534fa6c77d78a4a9633ee961344 100644 --- a/app/Views/admin/episode/edit.php +++ b/app/Views/admin/episode/edit.php @@ -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> - -<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> - -<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> - -<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> - -<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> - -<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_close() ?> + +<?= 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', +]) ?> + +<?= 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', +]) ?> + +<?= 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', +]) ?> + +<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> + +<?= 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> - -<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> +<?= form_fieldset_close() ?> + +<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() ?> diff --git a/app/Views/admin/episode/list.php b/app/Views/admin/episode/list.php index 59f457c40da05135c384f093fca3599634098ca1..c6cfe92da31986c270bb7176863a0c7d039cf3e8 100644 --- a/app/Views/admin/episode/list.php +++ b/app/Views/admin/episode/list.php @@ -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') ?> diff --git a/app/Views/admin/episode/view.php b/app/Views/admin/episode/view.php index 90b9a343ae0a12d6a343fae359963f41d3844430..2b154a6dd5231416ec7811c8e52302a756ca265c 100644 --- a/app/Views/admin/episode/view.php +++ b/app/Views/admin/episode/view.php @@ -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( diff --git a/app/Views/admin/my_account/change_password.php b/app/Views/admin/my_account/change_password.php index 619085d8261b35e71083cfe96a13389a21b8fef3..285f058e6e0649ddc9179672595260e1e1c34219 100644 --- a/app/Views/admin/my_account/change_password.php +++ b/app/Views/admin/my_account/change_password.php @@ -7,28 +7,37 @@ <?= $this->section('content') ?> -<form action="<?= route_to( - 'myAccount_changePassword' -) ?>" method="post" class="flex flex-col max-w-lg"> - <?= csrf_field() ?> - - <input type="hidden" name="email" value="<?= user()->email ?>"> - - <label for="password"><?= lang('User.form.password') ?></label> - <input type="password" name="password" class="mb-4 form-input" id="password" autocomplete="off"> - - <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"> - - <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_open(route_to('change-password'), [ + 'class' => 'flex flex-col max-w-sm', +]) ?> +<?= csrf_field() ?> + +<?= form_label(lang('User.form.password'), 'password') ?> +<?= form_input([ + 'id' => 'password', + 'name' => 'password', + 'class' => 'form-input mb-4', + 'required' => 'required', + 'type' => 'password', +]) ?> + +<?= 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', +]) ?> + +<?= form_button([ + 'content' => lang('User.form.submit_password_change'), + 'type' => 'submit', + 'class' => 'self-end px-4 py-2 bg-gray-200', +]) ?> + +<?= form_close() ?> <?= $this->endSection() ?> diff --git a/app/Views/admin/podcast/create.php b/app/Views/admin/podcast/create.php index 299ab325c60d6b821a16eb5ff7d42a6afba62c48..498199051fb7dbea0838fd9782fb6195569b2142 100644 --- a/app/Views/admin/podcast/create.php +++ b/app/Views/admin/podcast/create.php @@ -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 /> -</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> +<?= form_label(lang('Podcast.form.title'), 'title') ?> +<?= form_input([ + 'id' => 'title', + 'name' => 'title', + 'class' => 'form-input mb-4', + 'value' => old('title'), + 'required' => 'required', +]) ?> -<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> +<?= 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="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.description'), 'description') ?> + <?= form_textarea( + [ + 'id' => 'description', + 'name' => 'description', + 'class' => 'form-textarea', + 'required' => 'required', + ], + old('description', '', false), + 'data-editor="markdown"' + ) ?> +</div> + +<div class="mb-4"> + <?= form_label( + lang('Podcast.form.episode_description_footer'), '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" required /> -</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 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> - -<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_textarea( + [ + 'id' => 'episode_description_footer', + 'name' => 'episode_description_footer', + 'class' => 'form-textarea', + ], + old('episode_description_footer', '', false), + 'data-editor="markdown"' + ) ?> +</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="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> +<?= 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="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.category'), 'category') ?> +<?= form_dropdown('category', $categoryOptions, old('category'), [ + 'id' => 'category', + 'class' => 'form-select mb-4', + 'required' => 'required', +]) ?> -<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> +<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> + +<?= 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="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_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_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> +<?= 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', +]) ?> -<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> - -<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' - ) ?>" /> -</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> - -<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> +<?= form_fieldset_close() ?> + +<?= form_label(lang('Podcast.form.copyright'), 'copyright') ?> +<?= form_input([ + 'id' => 'copyright', + 'name' => 'copyright', + 'class' => 'form-input mb-4', + 'value' => old('copyright'), +]) ?> -<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> +<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> + +<?= form_button([ + 'content' => lang('Podcast.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( - 'Podcast.form.submit_create' -) ?></button> <?= form_close() ?> diff --git a/app/Views/admin/podcast/edit.php b/app/Views/admin/podcast/edit.php index 434937feed25e380a43808da37a3e88ce5446717..b40f82fd37d8a470eb1548d202dd0976e8ac1307 100644 --- a/app/Views/admin/podcast/edit.php +++ b/app/Views/admin/podcast/edit.php @@ -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 /> -</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> - -<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> -<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> +<?= 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', +]) ?> -<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> +<?= 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="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="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="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> + +<?= 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="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> - -<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> +<?= 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_email"><?= lang('Podcast.form.author_email') ?></label> - <input type="email" class="form-input" id="author_email" name="author_email" value="<?= $podcast->author_email ?>" /> -</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> + +<?= 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> - -<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 ?>" /> -</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> - -<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> +<?= form_fieldset_close() ?> + +<?= form_label(lang('Podcast.form.copyright'), 'copyright') ?> +<?= form_input([ + 'id' => 'copyright', + 'name' => 'copyright', + 'class' => 'form-input mb-4', + 'value' => old('copyright', $podcast->copyright), +]) ?> -<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> +<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> + +<?= form_button([ + 'content' => lang('Podcast.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( - 'Podcast.form.submit_edit' -) ?></button> <?= form_close() ?> diff --git a/app/Views/admin/podcast/list.php b/app/Views/admin/podcast/list.php index 2efbe4d931ec7a74c120636ebd79d2f1c16be014..83768a7cbefe70646fce062f16604c892ea6e964 100644 --- a/app/Views/admin/podcast/list.php +++ b/app/Views/admin/podcast/list.php @@ -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> diff --git a/app/Views/admin/podcast/view.php b/app/Views/admin/podcast/view.php index 3f21185e41a7191bae3a312492f63102d246bb7a..013e22aa82b3b170b8477dcdcba2f4a57f135c8c 100644 --- a/app/Views/admin/podcast/view.php +++ b/app/Views/admin/podcast/view.php @@ -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> diff --git a/app/Views/admin/user/create.php b/app/Views/admin/user/create.php index 865521ce52edfcaae6e9c4bbf7f6d7f4b6320e02..9b860c1bf516f654695f7e8c7af2c357a6cc1c43 100644 --- a/app/Views/admin/user/create.php +++ b/app/Views/admin/user/create.php @@ -7,31 +7,44 @@ <?= $this->section('content') ?> -<form action="<?= route_to( - 'user_create' -) ?>" method="post" class="flex flex-col max-w-lg"> - <?= 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' - ) ?>"> - - <label for="username"><?= lang('User.form.username') ?></label> - <input type="text" class="mb-4 form-input" name="username" id="username" 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"> - - <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"> - - <button type="submit" class="px-4 py-2 ml-auto border"> - <?= lang('User.form.submit_create') ?> - </button> -</form> +<?= form_open(route_to('user-create'), [ + 'class' => 'flex flex-col max-w-sm', +]) ?> +<?= csrf_field() ?> + +<?= form_label(lang('User.form.email'), 'email') ?> +<?= form_input([ + 'id' => 'email', + 'name' => 'email', + 'class' => 'form-input mb-4', + 'value' => old('email'), + 'type' => 'email', +]) ?> + +<?= form_label(lang('User.form.username'), 'username') ?> +<?= form_input([ + 'id' => 'username', + 'name' => 'username', + 'class' => 'form-input mb-4', + 'value' => old('username'), +]) ?> + +<?= form_label(lang('User.form.password'), 'password') ?> +<?= form_input([ + 'id' => 'password', + 'name' => 'password', + 'class' => 'form-input mb-4', + 'type' => 'password', + 'autocomplete' => 'new-password', +]) ?> + +<?= form_button([ + 'content' => lang('User.form.submit_create'), + 'type' => 'submit', + 'class' => 'self-end px-4 py-2 bg-gray-200', +]) ?> + +<?= form_close() ?> <?= $this->endSection() ?> diff --git a/app/Views/admin/user/edit.php b/app/Views/admin/user/edit.php index dc3594690260473535d5e32be266df832d0c5c4f..ef9fc1f287ddb84cae83f36fb434a58e8cebd556 100644 --- a/app/Views/admin/user/edit.php +++ b/app/Views/admin/user/edit.php @@ -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() ?> - - <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> - - <button type="submit" class="px-4 py-2 ml-auto border"> - <?= lang('User.form.submit_edit') ?> - </button> -</form> +<?= form_open(route_to('user-edit', $user->id), [ + 'class' => 'flex flex-col max-w-sm', +]) ?> +<?= csrf_field() ?> + +<?= form_label(lang('User.form.roles'), 'roles') ?> +<?= form_multiselect('roles[]', $roleOptions, $user->roles, [ + 'id' => 'roles', + 'class' => 'form-multiselect mb-4', +]) ?> + +<?= form_button([ + 'content' => lang('User.form.submit_edit'), + 'type' => 'submit', + 'class' => 'self-end px-4 py-2 bg-gray-200', +]) ?> + +<?= form_close() ?> <?= $this->endSection() ?> diff --git a/app/Views/admin/user/list.php b/app/Views/admin/user/list.php index a8d096358b4d1d83b1ab3f24b7acf5f9083d51df..f47b3b22202826dbce35ee1872544c5fe85c4f09 100644 --- a/app/Views/admin/user/list.php +++ b/app/Views/admin/user/list.php @@ -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> diff --git a/app/Views/auth/_layout.php b/app/Views/auth/_layout.php index c0cae49f718f7f21e04c7c708d8cf31b1a307cb2..5f9c8de38fbd9d02781de4187dd55e7b46512785 100644 --- a/app/Views/auth/_layout.php +++ b/app/Views/auth/_layout.php @@ -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> diff --git a/app/Views/auth/forgot.php b/app/Views/auth/forgot.php index cef9a088052f034f738ab34d9e5fde6ce0b1e40a..7a742021a710b867782d3649a8d42ffca47a24e6 100644 --- a/app/Views/auth/forgot.php +++ b/app/Views/auth/forgot.php @@ -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() ?> diff --git a/app/Views/auth/login.php b/app/Views/auth/login.php index 106851d06d31a4ff5cf62e347c326ff3c4095256..c231de542318505fbc50a337388008073d4c4c59 100644 --- a/app/Views/auth/login.php +++ b/app/Views/auth/login.php @@ -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() ?> - - <label for="login"><?= lang('Auth.emailOrUsername') ?></label> - <input type="text" name="login" class="mb-4 form-input" placeholder="<?= lang( - 'Auth.emailOrUsername' - ) ?>"> - - <label for="password"><?= lang('Auth.password') ?></label> - <input type="password" name="password" class="mb-6 form-input" placeholder="<?= lang( - 'Auth.password' - ) ?>"> - - <button type="submit" class="px-4 py-2 ml-auto border"> - <?= lang('Auth.loginAction') ?> - </button> -</form> +<?= form_open(route_to('login'), ['class' => 'flex flex-col']) ?> +<?= csrf_field() ?> + +<?= form_label(lang('Auth.emailOrUsername'), 'login') ?> +<?= form_input([ + 'id' => 'login', + 'name' => 'login', + 'class' => 'form-input mb-4', + 'required' => 'required', +]) ?> + +<?= form_label(lang('Auth.password'), 'password') ?> +<?= form_input([ + 'id' => 'password', + 'name' => 'password', + 'class' => 'form-input mb-4', + 'type' => 'password', + 'required' => 'required', +]) ?> + +<?= form_button([ + 'content' => lang('Auth.loginAction'), + 'class' => 'px-4 py-2 ml-auto border', + 'type' => 'submit', +]) ?> + +<?= form_close() ?> <?= $this->endSection() ?> diff --git a/app/Views/auth/register.php b/app/Views/auth/register.php index 571aff38fc608a0121b01f7e0b621867f88010a6..aa2a7db6d4c348b555f8450598f003ce993f9632 100644 --- a/app/Views/auth/register.php +++ b/app/Views/auth/register.php @@ -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() ?> - - <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> - - <label for="username"><?= lang('Auth.username') ?></label> - <input type="text" class="mb-4 form-input" name="username" placeholder="<?= lang( - 'Auth.username' - ) ?>" value="<?= old('username') ?>"> - - <label for="password"><?= lang('Auth.password') ?></label> - <input type="password" name="password" class="mb-4 form-input" placeholder="<?= lang( - 'Auth.password' - ) ?>" autocomplete="off"> - - <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"> - - <button type="submit" class="px-4 py-2 ml-auto border"> - <?= lang('Auth.register') ?> - </button> -</form> +<?= form_open(route_to('register'), ['class' => 'flex flex-col']) ?> +<?= csrf_field() ?> + +<?= 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> + +<?= form_label(lang('Auth.username'), 'username') ?> +<?= form_input([ + 'id' => 'username', + 'name' => 'username', + 'class' => 'form-input mb-4', + 'value' => old('username'), + 'required' => 'required', +]) ?> + +<?= form_label(lang('Auth.password'), 'password') ?> +<?= form_input([ + 'id' => 'password', + 'name' => 'password', + 'class' => 'form-input mb-4', + 'type' => 'password', + 'required' => 'required', + 'autocomplete' => 'new-password', +]) ?> + +<?= form_button([ + 'content' => lang('Auth.register'), + 'class' => 'px-4 py-2 ml-auto border', + 'type' => 'submit', +]) ?> + +<?= form_close() ?> <?= $this->endSection() ?> diff --git a/app/Views/auth/reset.php b/app/Views/auth/reset.php index d2bfbce9b49d533c426014268208552706c8e5ce..8e26e72f2ad4db5b616f636da811f834989db252 100644 --- a/app/Views/auth/reset.php +++ b/app/Views/auth/reset.php @@ -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() ?> - - <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 ?? '') ?>"> - - <label for="email"><?= lang('Auth.email') ?></label> - <input type="email" class="mb-4 form-input" name="email" placeholder="<?= lang( - 'Auth.email' - ) ?>" value="<?= old('email') ?>"> - - <label for="password"><?= lang('Auth.newPassword') ?></label> - <input type="password" class="mb-4 form-input" name="password"> - - <label for="pass_confirm"><?= lang('Auth.newPasswordRepeat') ?></label> - <input type="password" class="mb-6 form-input" name="pass_confirm"> - - <button type="submit" class="px-4 py-2 ml-auto border"> - <?= lang('Auth.resetPassword') ?> - </button> -</form> +<?= form_open(route_to('reset-password'), ['class' => 'flex flex-col']) ?> +<?= csrf_field() ?> + +<?= form_label(lang('Auth.token'), 'token') ?> +<?= form_input([ + 'id' => 'token', + 'name' => 'token', + 'class' => 'form-input mb-4', + 'value' => old('token', $token ?? ''), + 'required' => 'required', +]) ?> + +<?= form_label(lang('Auth.email'), 'email') ?> +<?= form_input([ + 'id' => 'email', + 'name' => 'email', + 'class' => 'form-input mb-4', + 'value' => old('email'), + 'required' => 'required', + 'type' => 'email', +]) ?> + +<?= form_label(lang('Auth.newPassword'), 'password') ?> +<?= form_input([ + 'id' => 'password', + 'name' => 'password', + 'class' => 'form-input mb-4', + 'type' => 'password', + 'required' => 'required', + 'autocomplete' => 'new-password', +]) ?> + +<?= form_button([ + 'content' => lang('Auth.resetPassword'), + 'class' => 'px-4 py-2 ml-auto border', + 'type' => 'submit', +]) ?> + +<?= form_close() ?> <?= $this->endSection() ?> diff --git a/app/Views/install/env.php b/app/Views/install/env.php index d41eff3fb5a07978828ef59ad52e529fd634c20c..d11b2a7f20fd216990035e36628bd8893449e71c 100644 --- a/app/Views/install/env.php +++ b/app/Views/install/env.php @@ -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') ?> diff --git a/app/Views/install/superadmin.php b/app/Views/install/superadmin.php index 37858097ae05a93c3f8b14d56735da72f254cc7f..d4ddc61f976b77a808f9f59cb56774a78c746173 100644 --- a/app/Views/install/superadmin.php +++ b/app/Views/install/superadmin.php @@ -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() ?>