From c63a077618c61b4cde7f25ffc650a4b0e1495f44 Mon Sep 17 00:00:00 2001
From: Yassine Doghri
Date: Fri, 10 Jul 2020 12:20:25 +0000
Subject: [PATCH] feat(users): add myth-auth to handle users crud + add admin
gateway only accessible by login
- overwrite myth/auth config with castopod app needs
- create custom views for users authentication
- add admin area bootstrapped by admin controller
- shift podcast and episodes crud to admin area
- reorganize view layouts
- update docs for database migration
- add myth-auth to DEPENDENCIES.md
closes #11
---
DEPENDENCIES.md | 1 +
app/Config/App.php | 16 ++
app/Config/Auth.php | 42 ++++
app/Config/Filters.php | 12 ++
app/Config/Routes.php | 150 +++++++++++++--
app/Config/Toolbar.php | 1 +
app/Config/Validation.php | 1 +
app/Controllers/Admin/BaseController.php | 48 +++++
app/Controllers/Admin/Episode.php | 168 ++++++++++++++++
app/Controllers/Admin/Home.php | 16 ++
app/Controllers/Admin/Myaccount.php | 73 +++++++
app/Controllers/Admin/Podcast.php | 181 ++++++++++++++++++
app/Controllers/Admin/User.php | 142 ++++++++++++++
app/Controllers/Analytics.php | 4 +-
app/Controllers/Auth.php | 98 ++++++++++
app/Controllers/BaseController.php | 4 +-
app/Controllers/Episode.php | 137 +------------
app/Controllers/Feed.php | 5 +
app/Controllers/Home.php | 4 +-
app/Controllers/Podcast.php | 138 +------------
app/Controllers/UnknownUserAgents.php | 9 +-
.../2020-07-03-191500_add_users_podcasts.php | 48 +++++
app/Database/Seeds/UserSeeder.php | 30 +++
app/Entities/Episode.php | 2 +-
app/Entities/Podcast.php | 23 ++-
app/Entities/UserPodcast.php | 18 ++
app/Helpers/analytics_helper.php | 1 -
app/Helpers/media_helper.php | 2 +-
app/Language/en/Episode.php | 8 +
app/Language/en/Home.php | 5 +
app/Language/en/MyAccount.php | 11 ++
app/Language/en/Podcast.php | 10 +
app/Language/en/User.php | 28 +++
app/Models/EpisodeModel.php | 29 ++-
app/Models/PodcastModel.php | 6 +-
app/Models/UserPodcastModel.php | 23 +++
.../{layouts/default.php => _layout.php} | 9 +-
app/Views/_message_block.php | 20 ++
app/Views/admin/_layout.php | 37 ++++
app/Views/admin/_sidenav.php | 54 ++++++
app/Views/admin/dashboard.php | 8 +
app/Views/{ => admin}/episode/create.php | 2 +-
app/Views/{ => admin}/episode/edit.php | 2 +-
app/Views/admin/episode/list.php | 65 +++++++
.../admin/my_account/change_password.php | 31 +++
app/Views/admin/my_account/view.php | 31 +++
app/Views/{ => admin}/podcast/create.php | 2 +-
app/Views/{ => admin}/podcast/edit.php | 2 +-
app/Views/admin/podcast/list.php | 49 +++++
app/Views/admin/user/create.php | 38 ++++
app/Views/admin/user/list.php | 61 ++++++
app/Views/auth/_layout.php | 31 +++
app/Views/auth/change_password.php | 29 +++
app/Views/auth/emails/activation.php | 11 ++
app/Views/auth/emails/forgot.php | 13 ++
app/Views/auth/forgot.php | 25 +++
app/Views/auth/login.php | 44 +++++
app/Views/auth/register.php | 54 ++++++
app/Views/auth/reset.php | 38 ++++
app/Views/{episode/view.php => episode.php} | 21 +-
app/Views/home.php | 7 +-
app/Views/{podcast/view.php => podcast.php} | 28 +--
composer.json | 3 +-
composer.lock | 102 +++++++---
docs/setup-development.md | 2 +-
65 files changed, 1921 insertions(+), 392 deletions(-)
create mode 100644 app/Config/Auth.php
create mode 100644 app/Controllers/Admin/BaseController.php
create mode 100644 app/Controllers/Admin/Episode.php
create mode 100644 app/Controllers/Admin/Home.php
create mode 100644 app/Controllers/Admin/Myaccount.php
create mode 100644 app/Controllers/Admin/Podcast.php
create mode 100644 app/Controllers/Admin/User.php
create mode 100644 app/Controllers/Auth.php
create mode 100644 app/Database/Migrations/2020-07-03-191500_add_users_podcasts.php
create mode 100644 app/Database/Seeds/UserSeeder.php
create mode 100644 app/Entities/UserPodcast.php
create mode 100644 app/Language/en/MyAccount.php
create mode 100644 app/Language/en/User.php
create mode 100644 app/Models/UserPodcastModel.php
rename app/Views/{layouts/default.php => _layout.php} (77%)
create mode 100644 app/Views/_message_block.php
create mode 100644 app/Views/admin/_layout.php
create mode 100644 app/Views/admin/_sidenav.php
create mode 100644 app/Views/admin/dashboard.php
rename app/Views/{ => admin}/episode/create.php (98%)
rename app/Views/{ => admin}/episode/edit.php (99%)
create mode 100644 app/Views/admin/episode/list.php
create mode 100644 app/Views/admin/my_account/change_password.php
create mode 100644 app/Views/admin/my_account/view.php
rename app/Views/{ => admin}/podcast/create.php (99%)
rename app/Views/{ => admin}/podcast/edit.php (99%)
create mode 100644 app/Views/admin/podcast/list.php
create mode 100644 app/Views/admin/user/create.php
create mode 100644 app/Views/admin/user/list.php
create mode 100644 app/Views/auth/_layout.php
create mode 100644 app/Views/auth/change_password.php
create mode 100644 app/Views/auth/emails/activation.php
create mode 100644 app/Views/auth/emails/forgot.php
create mode 100644 app/Views/auth/forgot.php
create mode 100644 app/Views/auth/login.php
create mode 100644 app/Views/auth/register.php
create mode 100644 app/Views/auth/reset.php
rename app/Views/{episode/view.php => episode.php} (51%)
rename app/Views/{podcast/view.php => podcast.php} (63%)
diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md
index b859ea45..77fc51eb 100644
--- a/DEPENDENCIES.md
+++ b/DEPENDENCIES.md
@@ -13,3 +13,4 @@ Castopod uses the following components:
- [GeoIP2 PHP API](https://github.com/maxmind/GeoIP2-php) ([Apache License 2.0](https://github.com/maxmind/GeoIP2-php/blob/master/LICENSE))
- [Quill Rich Text Editor](https://github.com/quilljs/quill) ([BSD 3-Clause "New" or "Revised" License](https://github.com/quilljs/quill/blob/develop/LICENSE))
- [getID3](https://github.com/JamesHeinrich/getID3) ([GNU General Public License v3](https://github.com/JamesHeinrich/getID3/blob/2.0/licenses/license.gpl-30.txt))
+- [myth-auth](https://github.com/lonnieezell/myth-auth) ([MIT license](https://github.com/lonnieezell/myth-auth/blob/develop/LICENSE.md))
diff --git a/app/Config/App.php b/app/Config/App.php
index a12599ff..0f03019e 100644
--- a/app/Config/App.php
+++ b/app/Config/App.php
@@ -274,4 +274,20 @@ class App extends BaseConfig
| Defines the root folder for media files storage
*/
public $mediaRoot = 'media';
+
+ /*
+ |--------------------------------------------------------------------------
+ | Admin gateway
+ |--------------------------------------------------------------------------
+ | Defines a base route for all admin pages
+ */
+ public $adminGateway = 'admin';
+
+ /*
+ |--------------------------------------------------------------------------
+ | Auth gateway
+ |--------------------------------------------------------------------------
+ | Defines a base route for all authentication related pages
+ */
+ public $authGateway = 'auth';
}
diff --git a/app/Config/Auth.php b/app/Config/Auth.php
new file mode 100644
index 00000000..6c5b78f5
--- /dev/null
+++ b/app/Config/Auth.php
@@ -0,0 +1,42 @@
+ 'auth/login',
+ 'register' => 'auth/register',
+ 'forgot' => 'auth/forgot',
+ 'reset' => 'auth/reset',
+ 'emailForgot' => 'auth/emails/forgot',
+ 'emailActivation' => 'auth/emails/activation',
+ ];
+
+ //--------------------------------------------------------------------
+ // Layout for the views to extend
+ //--------------------------------------------------------------------
+
+ public $viewLayout = 'auth/_layout';
+
+ //--------------------------------------------------------------------
+ // Allow User Registration
+ //--------------------------------------------------------------------
+ // When enabled (default) any unregistered user may apply for a new
+ // account. If you disable registration you may need to ensure your
+ // controllers and views know not to offer registration.
+ //
+ public $allowRegistration = false;
+
+ //--------------------------------------------------------------------
+ // Require confirmation registration via email
+ //--------------------------------------------------------------------
+ // When enabled, every registered user will receive an email message
+ // with a special link he have to confirm to activate his account.
+ //
+ public $requireActivation = false;
+}
diff --git a/app/Config/Filters.php b/app/Config/Filters.php
index f76ceb20..fef58ff5 100644
--- a/app/Config/Filters.php
+++ b/app/Config/Filters.php
@@ -10,6 +10,9 @@ class Filters extends BaseConfig
'csrf' => \CodeIgniter\Filters\CSRF::class,
'toolbar' => \CodeIgniter\Filters\DebugToolbar::class,
'honeypot' => \CodeIgniter\Filters\Honeypot::class,
+ 'login' => \Myth\Auth\Filters\LoginFilter::class,
+ 'role' => \Myth\Auth\Filters\RoleFilter::class,
+ 'permission' => \Myth\Auth\Filters\PermissionFilter::class,
];
// Always applied before every request
@@ -33,4 +36,13 @@ class Filters extends BaseConfig
// that they should run on, like:
// 'isLoggedIn' => ['before' => ['account/*', 'profiles/*']],
public $filters = [];
+
+ public function __construct()
+ {
+ parent::__construct();
+
+ $this->filters = [
+ 'login' => ['before' => [config('App')->adminGateway . '*']],
+ ];
+ }
}
diff --git a/app/Config/Routes.php b/app/Config/Routes.php
index 5db584e1..494a3f62 100644
--- a/app/Config/Routes.php
+++ b/app/Config/Routes.php
@@ -24,6 +24,7 @@ $routes->set404Override();
$routes->setAutoRoute(false);
$routes->addPlaceholder('podcastName', '[a-zA-Z0-9\_]{1,191}');
$routes->addPlaceholder('episodeSlug', '[a-zA-Z0-9\-]{1,191}');
+$routes->addPlaceholder('username', '[a-zA-Z0-9 ]{3,}');
/**
* --------------------------------------------------------------------
@@ -34,28 +35,13 @@ $routes->addPlaceholder('episodeSlug', '[a-zA-Z0-9\-]{1,191}');
// We get a performance increase by specifying the default
// route since we don't have to scan directories.
$routes->get('/', 'Home::index', ['as' => 'home']);
-$routes->add('new-podcast', 'Podcast::create', ['as' => 'podcast_create']);
$routes->group('@(:podcastName)', function ($routes) {
- $routes->add('/', 'Podcast::view/$1', ['as' => 'podcast_view']);
- $routes->add('edit', 'Podcast::edit/$1', [
- 'as' => 'podcast_edit',
- ]);
- $routes->add('delete', 'Podcast::delete/$1', [
- 'as' => 'podcast_delete',
- ]);
+ $routes->add('/', 'Podcast/$1', ['as' => 'podcast']);
+
$routes->add('feed.xml', 'Feed/$1', ['as' => 'podcast_feed']);
- $routes->add('new-episode', 'Episode::create/$1', [
- 'as' => 'episode_create',
- ]);
- $routes->add('episodes/(:episodeSlug)', 'Episode::view/$1/$2', [
- 'as' => 'episode_view',
- ]);
- $routes->add('episodes/(:episodeSlug)/edit', 'Episode::edit/$1/$2', [
- 'as' => 'episode_edit',
- ]);
- $routes->add('episodes/(:episodeSlug)/delete', 'Episode::delete/$1/$2', [
- 'as' => 'episode_delete',
+ $routes->add('episodes/(:episodeSlug)', 'Episode/$1/$2', [
+ 'as' => 'episode',
]);
});
@@ -68,6 +54,132 @@ $routes->add('stats/(:num)/(:num)/(:any)', 'Analytics::hit/$1/$2/$3', [
$routes->add('.well-known/unknown-useragents', 'UnknownUserAgents');
$routes->add('.well-known/unknown-useragents/(:num)', 'UnknownUserAgents/$1');
+// Admin area
+$routes->group(
+ config('App')->adminGateway,
+ ['namespace' => 'App\Controllers\Admin'],
+ function ($routes) {
+ $routes->add('/', 'Home', [
+ 'as' => 'admin',
+ ]);
+
+ $routes->add('new-podcast', 'Podcast::create', [
+ 'as' => 'podcast_create',
+ ]);
+ $routes->add('podcasts', 'Podcast::list', ['as' => 'podcast_list']);
+
+ $routes->group('podcasts/@(:podcastName)', function ($routes) {
+ $routes->add('edit', 'Podcast::edit/$1', [
+ 'as' => 'podcast_edit',
+ ]);
+ $routes->add('delete', 'Podcast::delete/$1', [
+ 'as' => 'podcast_delete',
+ ]);
+
+ $routes->add('new-episode', 'Episode::create/$1', [
+ 'as' => 'episode_create',
+ ]);
+ $routes->add('episodes', 'Episode::list/$1', [
+ 'as' => 'episode_list',
+ ]);
+
+ $routes->add(
+ 'episodes/(:episodeSlug)/edit',
+ 'Episode::edit/$1/$2',
+ [
+ 'as' => 'episode_edit',
+ ]
+ );
+ $routes->add(
+ 'episodes/(:episodeSlug)/delete',
+ 'Episode::delete/$1/$2',
+ [
+ 'as' => 'episode_delete',
+ ]
+ );
+ });
+
+ // Users
+ $routes->add('users', 'User::list', ['as' => 'user_list']);
+ $routes->add('new-user', 'User::create', ['as' => 'user_create']);
+
+ $routes->add('users/@(:any)/ban', 'User::ban/$1', [
+ 'as' => 'user_ban',
+ ]);
+ $routes->add('users/@(:any)/unban', 'User::unBan/$1', [
+ 'as' => 'user_unban',
+ ]);
+ $routes->add(
+ 'users/@(:any)/force-pass-reset',
+ 'User::forcePassReset/$1',
+ [
+ 'as' => 'user_force_pass_reset',
+ ]
+ );
+
+ $routes->add('users/@(:any)/delete', 'User::delete/$1', [
+ 'as' => 'user_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',
+ ]
+ );
+ }
+);
+
+/**
+ * Overwriting Myth:auth routes file
+ */
+$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', 'Auth::register', [
+ 'as' => 'register',
+ ]);
+ $routes->post('register', 'Auth::attemptRegister');
+
+ // 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', 'Auth::forgotPassword', [
+ 'as' => 'forgot',
+ ]);
+ $routes->post('forgot', 'Auth::attemptForgot');
+ $routes->get('reset-password', 'Auth::resetPassword', [
+ 'as' => 'reset-password',
+ ]);
+ $routes->post('reset-password', 'Auth::attemptReset');
+ $routes->get('change-password', 'Auth::changePassword', [
+ 'as' => 'change_pass',
+ ]);
+ $routes->post('change-password', 'Auth::attemptChange');
+});
+
/**
* --------------------------------------------------------------------
* Additional Routing
diff --git a/app/Config/Toolbar.php b/app/Config/Toolbar.php
index 639d431a..14d3e7bd 100644
--- a/app/Config/Toolbar.php
+++ b/app/Config/Toolbar.php
@@ -25,6 +25,7 @@ class Toolbar extends BaseConfig
\CodeIgniter\Debug\Toolbar\Collectors\Files::class,
\CodeIgniter\Debug\Toolbar\Collectors\Routes::class,
\CodeIgniter\Debug\Toolbar\Collectors\Events::class,
+ \Myth\Auth\Collectors\Auth::class,
];
/*
diff --git a/app/Config/Validation.php b/app/Config/Validation.php
index ba4ac7cd..d93c623f 100644
--- a/app/Config/Validation.php
+++ b/app/Config/Validation.php
@@ -17,6 +17,7 @@ class Validation
\CodeIgniter\Validation\FormatRules::class,
\CodeIgniter\Validation\FileRules::class,
\CodeIgniter\Validation\CreditCardRules::class,
+ \Myth\Auth\Authentication\Passwords\ValidationRules::class,
];
/**
diff --git a/app/Controllers/Admin/BaseController.php b/app/Controllers/Admin/BaseController.php
new file mode 100644
index 00000000..a10692c9
--- /dev/null
+++ b/app/Controllers/Admin/BaseController.php
@@ -0,0 +1,48 @@
+session = \Config\Services::session();
+ }
+}
diff --git a/app/Controllers/Admin/Episode.php b/app/Controllers/Admin/Episode.php
new file mode 100644
index 00000000..105aed97
--- /dev/null
+++ b/app/Controllers/Admin/Episode.php
@@ -0,0 +1,168 @@
+podcast = $podcast_model->where('name', $params[0])->first();
+
+ if (count($params) > 1) {
+ $episode_model = new EpisodeModel();
+ if (
+ !($episode = $episode_model
+ ->where([
+ 'podcast_id' => $this->podcast->id,
+ 'slug' => $params[1],
+ ])
+ ->first())
+ ) {
+ throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
+ }
+ $this->episode = $episode;
+ }
+
+ return $this->$method();
+ }
+
+ public function list()
+ {
+ $episode_model = new EpisodeModel();
+
+ $data = [
+ 'podcast' => $this->podcast,
+ 'all_podcast_episodes' => $episode_model
+ ->where('podcast_id', $this->podcast->id)
+ ->find(),
+ ];
+
+ return view('admin/episode/list', $data);
+ }
+
+ public function create()
+ {
+ helper(['form']);
+
+ if (
+ !$this->validate([
+ 'enclosure' => 'uploaded[enclosure]|ext_in[enclosure,mp3,m4a]',
+ 'image' =>
+ 'uploaded[image]|is_image[image]|ext_in[image,jpg,png]|permit_empty',
+ 'title' => 'required',
+ 'slug' => 'required|regex_match[[a-zA-Z0-9\-]{1,191}]',
+ 'description' => 'required',
+ 'type' => 'required',
+ ])
+ ) {
+ $data = [
+ 'podcast' => $this->podcast,
+ ];
+
+ echo view('admin/episode/create', $data);
+ } else {
+ $new_episode = new \App\Entities\Episode([
+ 'podcast_id' => $this->podcast->id,
+ 'title' => $this->request->getVar('title'),
+ 'slug' => $this->request->getVar('slug'),
+ 'enclosure' => $this->request->getFile('enclosure'),
+ 'pub_date' => $this->request->getVar('pub_date'),
+ 'description' => $this->request->getVar('description'),
+ 'image' => $this->request->getFile('image'),
+ 'explicit' => $this->request->getVar('explicit') or false,
+ 'number' => $this->request->getVar('episode_number'),
+ 'season_number' => $this->request->getVar('season_number'),
+ 'type' => $this->request->getVar('type'),
+ 'author_name' => $this->request->getVar('author_name'),
+ 'author_email' => $this->request->getVar('author_email'),
+ 'block' => $this->request->getVar('block') or false,
+ ]);
+
+ $episode_model = new EpisodeModel();
+ $episode_model->save($new_episode);
+
+ return redirect()->route('episode_list', [$this->podcast->name]);
+ }
+ }
+
+ public function edit()
+ {
+ helper(['form']);
+
+ if (
+ !$this->validate([
+ 'enclosure' =>
+ 'uploaded[enclosure]|ext_in[enclosure,mp3,m4a]|permit_empty',
+ 'image' =>
+ 'uploaded[image]|is_image[image]|ext_in[image,jpg,png]|permit_empty',
+ 'title' => 'required',
+ 'slug' => 'required|regex_match[[a-zA-Z0-9\-]{1,191}]',
+ 'description' => 'required',
+ 'type' => 'required',
+ ])
+ ) {
+ $data = [
+ 'podcast' => $this->podcast,
+ 'episode' => $this->episode,
+ ];
+
+ echo view('admin/episode/edit', $data);
+ } else {
+ $this->episode->title = $this->request->getVar('title');
+ $this->episode->slug = $this->request->getVar('slug');
+ $this->episode->pub_date = $this->request->getVar('pub_date');
+ $this->episode->description = $this->request->getVar('description');
+ $this->episode->explicit =
+ ($this->request->getVar('explicit') or false);
+ $this->episode->number = $this->request->getVar('episode_number');
+ $this->episode->season_number = $this->request->getVar(
+ 'season_number'
+ )
+ ? $this->request->getVar('season_number')
+ : null;
+ $this->episode->type = $this->request->getVar('type');
+ $this->episode->author_name = $this->request->getVar('author_name');
+ $this->episode->author_email = $this->request->getVar(
+ 'author_email'
+ );
+ $this->episode->block = ($this->request->getVar('block') or false);
+
+ $enclosure = $this->request->getFile('enclosure');
+ if ($enclosure->isValid()) {
+ $this->episode->enclosure = $this->request->getFile(
+ 'enclosure'
+ );
+ }
+ $image = $this->request->getFile('image');
+ if ($image) {
+ $this->episode->image = $this->request->getFile('image');
+ }
+
+ $episode_model = new EpisodeModel();
+ $episode_model->save($this->episode);
+
+ return redirect()->route('episode_list', [$this->podcast->name]);
+ }
+ }
+
+ public function delete()
+ {
+ $episode_model = new EpisodeModel();
+ $episode_model->delete($this->episode->id);
+
+ return redirect()->route('episode_list', [$this->podcast->name]);
+ }
+}
diff --git a/app/Controllers/Admin/Home.php b/app/Controllers/Admin/Home.php
new file mode 100644
index 00000000..6e3b80aa
--- /dev/null
+++ b/app/Controllers/Admin/Home.php
@@ -0,0 +1,16 @@
+ 'required|valid_email',
+ 'password' => 'required',
+ 'new_password' => 'required|strong_password',
+ 'new_pass_confirm' => 'required|matches[new_password]',
+ ];
+
+ if (!$this->validate($rules)) {
+ return redirect()
+ ->back()
+ ->withInput()
+ ->with('errors', $user_model->errors());
+ }
+
+ $credentials = [
+ 'email' => user()->email,
+ 'password' => $this->request->getPost('password'),
+ ];
+
+ if (!$auth->validate($credentials)) {
+ return redirect()
+ ->back()
+ ->withInput()
+ ->with('errors', $user_model->errors());
+ }
+
+ user()->password = $this->request->getPost('new_password');
+ $user_model->save(user());
+
+ if (!$user_model->save(user())) {
+ return redirect()
+ ->back()
+ ->withInput()
+ ->with('errors', $user_model->errors());
+ }
+
+ // Success!
+ return redirect()
+ ->route('myAccount')
+ ->with('message', lang('MyAccount.passwordChangeSuccess'));
+ }
+}
diff --git a/app/Controllers/Admin/Podcast.php b/app/Controllers/Admin/Podcast.php
new file mode 100644
index 00000000..502f1532
--- /dev/null
+++ b/app/Controllers/Admin/Podcast.php
@@ -0,0 +1,181 @@
+ 0) {
+ $podcast_model = new PodcastModel();
+ if (
+ !($podcast = $podcast_model->where('name', $params[0])->first())
+ ) {
+ throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
+ }
+ $this->podcast = $podcast;
+ }
+
+ return $this->$method();
+ }
+
+ public function list()
+ {
+ $podcast_model = new PodcastModel();
+
+ $data = ['all_podcasts' => $podcast_model->findAll()];
+
+ return view('admin/podcast/list', $data);
+ }
+
+ public function create()
+ {
+ helper(['form', 'misc']);
+ $podcast_model = new PodcastModel();
+
+ if (
+ !$this->validate([
+ 'title' => 'required',
+ 'name' => 'required|regex_match[[a-zA-Z0-9\_]{1,191}]',
+ 'description' => 'required|max_length[4000]',
+ 'image' =>
+ 'uploaded[image]|is_image[image]|ext_in[image,jpg,png]',
+ 'owner_email' => 'required|valid_email',
+ 'type' => 'required',
+ ])
+ ) {
+ $languageModel = new LanguageModel();
+ $categoryModel = new CategoryModel();
+ $data = [
+ 'languages' => $languageModel->findAll(),
+ 'categories' => $categoryModel->findAll(),
+ 'browser_lang' => get_browser_language(
+ $this->request->getServer('HTTP_ACCEPT_LANGUAGE')
+ ),
+ ];
+
+ echo view('admin/podcast/create', $data);
+ } else {
+ $podcast = new \App\Entities\Podcast([
+ 'title' => $this->request->getVar('title'),
+ 'name' => $this->request->getVar('name'),
+ 'description' => $this->request->getVar('description'),
+ 'episode_description_footer' => $this->request->getVar(
+ 'episode_description_footer'
+ ),
+ 'image' => $this->request->getFile('image'),
+ 'language' => $this->request->getVar('language'),
+ 'category' => $this->request->getVar('category'),
+ 'explicit' => $this->request->getVar('explicit') or false,
+ 'author_name' => $this->request->getVar('author_name'),
+ 'author_email' => $this->request->getVar('author_email'),
+ 'owner_name' => $this->request->getVar('owner_name'),
+ 'owner_email' => $this->request->getVar('owner_email'),
+ 'type' => $this->request->getVar('type'),
+ 'copyright' => $this->request->getVar('copyright'),
+ 'block' => $this->request->getVar('block') or false,
+ 'complete' => $this->request->getVar('complete') or false,
+ 'custom_html_head' => $this->request->getVar(
+ 'custom_html_head'
+ ),
+ ]);
+
+ $db = \Config\Database::connect();
+
+ $db->transStart();
+
+ $new_podcast_id = $podcast_model->insert($podcast, true);
+
+ $user_podcast_model = new \App\Models\UserPodcastModel();
+ $user_podcast_model->save([
+ 'user_id' => user()->id,
+ 'podcast_id' => $new_podcast_id,
+ ]);
+
+ $db->transComplete();
+
+ return redirect()->route('podcast_list', [$podcast->name]);
+ }
+ }
+
+ public function edit()
+ {
+ helper(['form', 'misc']);
+
+ if (
+ !$this->validate([
+ 'title' => 'required',
+ 'name' => 'required|regex_match[[a-zA-Z0-9\_]{1,191}]',
+ 'description' => 'required|max_length[4000]',
+ 'image' =>
+ 'uploaded[image]|is_image[image]|ext_in[image,jpg,png]|permit_empty',
+ 'owner_email' => 'required|valid_email',
+ 'type' => 'required',
+ ])
+ ) {
+ $languageModel = new LanguageModel();
+ $categoryModel = new CategoryModel();
+ $data = [
+ 'podcast' => $this->podcast,
+ 'languages' => $languageModel->findAll(),
+ 'categories' => $categoryModel->findAll(),
+ ];
+
+ echo view('admin/podcast/edit', $data);
+ } else {
+ $this->podcast->title = $this->request->getVar('title');
+ $this->podcast->name = $this->request->getVar('name');
+ $this->podcast->description = $this->request->getVar('description');
+ $this->podcast->episode_description_footer = $this->request->getVar(
+ 'episode_description_footer'
+ );
+
+ $image = $this->request->getFile('image');
+ if ($image->isValid()) {
+ $this->podcast->image = $this->request->getFile('image');
+ }
+ $this->podcast->language = $this->request->getVar('language');
+ $this->podcast->category = $this->request->getVar('category');
+ $this->podcast->explicit =
+ ($this->request->getVar('explicit') or false);
+ $this->podcast->author_name = $this->request->getVar('author_name');
+ $this->podcast->author_email = $this->request->getVar(
+ 'author_email'
+ );
+ $this->podcast->owner_name = $this->request->getVar('owner_name');
+ $this->podcast->owner_email = $this->request->getVar('owner_email');
+ $this->podcast->type = $this->request->getVar('type');
+ $this->podcast->copyright = $this->request->getVar('copyright');
+ $this->podcast->block = ($this->request->getVar('block') or false);
+ $this->podcast->complete =
+ ($this->request->getVar('complete') or false);
+ $this->podcast->custom_html_head = $this->request->getVar(
+ 'custom_html_head'
+ );
+
+ $podcast_model = new PodcastModel();
+ $podcast_model->save($this->podcast);
+
+ return redirect()->route('podcast_list', [$this->podcast->name]);
+ }
+ }
+
+ public function delete()
+ {
+ $podcast_model = new PodcastModel();
+ $podcast_model->delete($this->podcast->id);
+
+ return redirect()->route('podcast_list');
+ }
+}
diff --git a/app/Controllers/Admin/User.php b/app/Controllers/Admin/User.php
new file mode 100644
index 00000000..4faffc23
--- /dev/null
+++ b/app/Controllers/Admin/User.php
@@ -0,0 +1,142 @@
+ 0) {
+ $user_model = new UserModel();
+ if (
+ !($user = $user_model->where('username', $params[0])->first())
+ ) {
+ throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
+ }
+ $this->user = $user;
+ }
+
+ return $this->$method();
+ }
+
+ public function list()
+ {
+ $user_model = new UserModel();
+
+ $data = ['all_users' => $user_model->findAll()];
+
+ return view('admin/user/list', $data);
+ }
+
+ public function create()
+ {
+ $user_model = new UserModel();
+
+ // Validate here first, since some things,
+ // like the password, can only be validated properly here.
+ $rules = array_merge(
+ $user_model->getValidationRules(['only' => ['username']]),
+ [
+ 'email' => 'required|valid_email|is_unique[users.email]',
+ 'password' => 'required|strong_password',
+ 'pass_confirm' => 'required|matches[password]',
+ ]
+ );
+
+ if (!$this->validate($rules)) {
+ echo view('admin/user/create');
+ } else {
+ // Save the user
+ $user = new \Myth\Auth\Entities\User($this->request->getPost());
+
+ // Activate user
+ $user->activate();
+
+ // Force user to reset his password on first connection
+ $user->force_pass_reset = true;
+ $user->generateResetHash();
+
+ if (!$user_model->save($user)) {
+ return redirect()
+ ->back()
+ ->withInput()
+ ->with('errors', $user_model->errors());
+ }
+
+ // Success!
+ return redirect()
+ ->route('user_list')
+ ->with('message', lang('User.createSuccess'));
+ }
+ }
+
+ public function forcePassReset()
+ {
+ $user_model = new UserModel();
+
+ $this->user->force_pass_reset = true;
+ $this->user->generateResetHash();
+
+ if (!$user_model->save($this->user)) {
+ return redirect()
+ ->back()
+ ->with('errors', $user_model->errors());
+ }
+
+ // Success!
+ return redirect()
+ ->route('user_list')
+ ->with('message', lang('User.forcePassResetSuccess'));
+ }
+
+ public function ban()
+ {
+ $user_model = new UserModel();
+ $this->user->ban('');
+
+ if (!$user_model->save($this->user)) {
+ return redirect()
+ ->back()
+ ->with('errors', $user_model->errors());
+ }
+
+ return redirect()
+ ->route('user_list')
+ ->with('message', lang('User.banSuccess'));
+ }
+
+ public function unBan()
+ {
+ $user_model = new UserModel();
+ $this->user->unBan();
+
+ if (!$user_model->save($this->user)) {
+ return redirect()
+ ->back()
+ ->with('errors', $user_model->errors());
+ }
+
+ return redirect()
+ ->route('user_list')
+ ->with('message', lang('User.unbanSuccess'));
+ }
+
+ public function delete()
+ {
+ $user_model = new UserModel();
+ $user_model->delete($this->user->id);
+
+ return redirect()
+ ->route('user_list')
+ ->with('message', lang('User.deleteSuccess'));
+ }
+}
diff --git a/app/Controllers/Analytics.php b/app/Controllers/Analytics.php
index f001a45a..9a2621c2 100644
--- a/app/Controllers/Analytics.php
+++ b/app/Controllers/Analytics.php
@@ -1,4 +1,4 @@
- $this->config,
+ 'email' => user()->email,
+ 'token' => user()->reset_hash,
+ ]);
+ }
+
+ public function attemptChange()
+ {
+ $users = new 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',
+ 'pass_confirm' => 'required|matches[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/Controllers/BaseController.php b/app/Controllers/BaseController.php
index 5e3a8c02..58cd1276 100644
--- a/app/Controllers/BaseController.php
+++ b/app/Controllers/BaseController.php
@@ -1,7 +1,5 @@
$method();
}
- public function create()
- {
- helper(['form']);
-
- if (
- !$this->validate([
- 'enclosure' => 'uploaded[enclosure]|ext_in[enclosure,mp3,m4a]',
- 'image' =>
- 'uploaded[image]|is_image[image]|ext_in[image,jpg,png]|permit_empty',
- 'title' => 'required',
- 'slug' => 'required|regex_match[[a-zA-Z0-9\-]{1,191}]',
- 'description' => 'required',
- 'type' => 'required',
- ])
- ) {
- $data = [
- 'podcast' => $this->podcast,
- ];
-
- echo view('episode/create', $data);
- } else {
- $new_episode = new \App\Entities\Episode([
- 'podcast_id' => $this->podcast->id,
- 'title' => $this->request->getVar('title'),
- 'slug' => $this->request->getVar('slug'),
- 'enclosure' => $this->request->getFile('enclosure'),
- 'pub_date' => $this->request->getVar('pub_date'),
- 'description' => $this->request->getVar('description'),
- 'image' => $this->request->getFile('image'),
- 'explicit' => $this->request->getVar('explicit') or false,
- 'number' => $this->request->getVar('episode_number'),
- 'season_number' => $this->request->getVar('season_number')
- ? $this->request->getVar('season_number')
- : null,
- 'type' => $this->request->getVar('type'),
- 'author_name' => $this->request->getVar('author_name'),
- 'author_email' => $this->request->getVar('author_email'),
- 'block' => $this->request->getVar('block') or false,
- ]);
-
- $episode_model = new EpisodeModel();
- $episode_model->save($new_episode);
-
- return redirect()->to(
- base_url(
- route_to(
- 'episode_view',
- $this->podcast->name,
- $new_episode->slug
- )
- )
- );
- }
- }
-
- public function edit()
- {
- helper(['form']);
-
- if (
- !$this->validate([
- 'enclosure' =>
- 'uploaded[enclosure]|ext_in[enclosure,mp3,m4a]|permit_empty',
- 'image' =>
- 'uploaded[image]|is_image[image]|ext_in[image,jpg,png]|permit_empty',
- 'title' => 'required',
- 'slug' => 'required|regex_match[[a-zA-Z0-9\-]{1,191}]',
- 'description' => 'required',
- 'type' => 'required',
- ])
- ) {
- $data = [
- 'podcast' => $this->podcast,
- 'episode' => $this->episode,
- ];
-
- echo view('episode/edit', $data);
- } else {
- $this->episode->title = $this->request->getVar('title');
- $this->episode->slug = $this->request->getVar('slug');
- $this->episode->pub_date = $this->request->getVar('pub_date');
- $this->episode->description = $this->request->getVar('description');
- $this->episode->explicit =
- ($this->request->getVar('explicit') or false);
- $this->episode->number = $this->request->getVar('episode_number');
- $this->episode->season_number = $this->request->getVar(
- 'season_number'
- )
- ? $this->request->getVar('season_number')
- : null;
- $this->episode->type = $this->request->getVar('type');
- $this->episode->author_name = $this->request->getVar('author_name');
- $this->episode->author_email = $this->request->getVar(
- 'author_email'
- );
- $this->episode->block = ($this->request->getVar('block') or false);
-
- $enclosure = $this->request->getFile('enclosure');
- if ($enclosure->isValid()) {
- $this->episode->enclosure = $this->request->getFile(
- 'enclosure'
- );
- }
- $image = $this->request->getFile('image');
- if ($image) {
- $this->episode->image = $this->request->getFile('image');
- }
-
- $episode_model = new EpisodeModel();
- $episode_model->save($this->episode);
-
- return redirect()->to(
- base_url(
- route_to(
- 'episode_view',
- $this->podcast->name,
- $this->episode->slug
- )
- )
- );
- }
- }
-
- public function view()
+ public function index()
{
// The page cache is set to a decade so it is deleted manually upon podcast update
$this->cachePage(DECADE);
@@ -173,16 +50,6 @@ class Episode extends BaseController
'podcast' => $this->podcast,
'episode' => $this->episode,
];
- return view('episode/view', $data);
- }
-
- public function delete()
- {
- $episode_model = new EpisodeModel();
- $episode_model->delete($this->episode->id);
-
- return redirect()->to(
- base_url(route_to('podcast_view', $this->podcast->name))
- );
+ return view('episode', $data);
}
}
diff --git a/app/Controllers/Feed.php b/app/Controllers/Feed.php
index e76eb199..48064d0a 100644
--- a/app/Controllers/Feed.php
+++ b/app/Controllers/Feed.php
@@ -1,4 +1,9 @@
to(
- base_url(route_to('podcast_view', $all_podcasts[0]->name))
- );
+ return redirect()->route('podcast', [$all_podcasts[0]->name]);
}
// default behavior: list all podcasts on home page
diff --git a/app/Controllers/Podcast.php b/app/Controllers/Podcast.php
index 081f7b9f..70db9d60 100644
--- a/app/Controllers/Podcast.php
+++ b/app/Controllers/Podcast.php
@@ -6,8 +6,6 @@
*/
namespace App\Controllers;
-use App\Models\CategoryModel;
-use App\Models\LanguageModel;
use App\Models\PodcastModel;
class Podcast extends BaseController
@@ -29,131 +27,7 @@ class Podcast extends BaseController
return $this->$method();
}
- public function create()
- {
- helper(['form', 'misc']);
- $podcast_model = new PodcastModel();
-
- if (
- !$this->validate([
- 'title' => 'required',
- 'name' => 'required|regex_match[[a-zA-Z0-9\_]{1,191}]',
- 'description' => 'required|max_length[4000]',
- 'image' =>
- 'uploaded[image]|is_image[image]|ext_in[image,jpg,png]',
- 'owner_email' => 'required|valid_email',
- 'type' => 'required',
- ])
- ) {
- $languageModel = new LanguageModel();
- $categoryModel = new CategoryModel();
- $data = [
- 'languages' => $languageModel->findAll(),
- 'categories' => $categoryModel->findAll(),
- 'browser_lang' => get_browser_language(
- $this->request->getServer('HTTP_ACCEPT_LANGUAGE')
- ),
- ];
-
- echo view('podcast/create', $data);
- } else {
- $podcast = new \App\Entities\Podcast([
- 'title' => $this->request->getVar('title'),
- 'name' => $this->request->getVar('name'),
- 'description' => $this->request->getVar('description'),
- 'episode_description_footer' => $this->request->getVar(
- 'episode_description_footer'
- ),
- 'image' => $this->request->getFile('image'),
- 'language' => $this->request->getVar('language'),
- 'category' => $this->request->getVar('category'),
- 'explicit' => $this->request->getVar('explicit') or false,
- 'author_name' => $this->request->getVar('author_name'),
- 'author_email' => $this->request->getVar('author_email'),
- 'owner_name' => $this->request->getVar('owner_name'),
- 'owner_email' => $this->request->getVar('owner_email'),
- 'type' => $this->request->getVar('type'),
- 'copyright' => $this->request->getVar('copyright'),
- 'block' => $this->request->getVar('block') or false,
- 'complete' => $this->request->getVar('complete') or false,
- 'custom_html_head' => $this->request->getVar(
- 'custom_html_head'
- ),
- ]);
-
- $podcast_model->save($podcast);
-
- return redirect()->to(
- base_url(route_to('podcast_view', $podcast->name))
- );
- }
- }
-
- public function edit()
- {
- helper(['form', 'misc']);
-
- if (
- !$this->validate([
- 'title' => 'required',
- 'name' => 'required|regex_match[[a-zA-Z0-9\_]{1,191}]',
- 'description' => 'required|max_length[4000]',
- 'image' =>
- 'uploaded[image]|is_image[image]|ext_in[image,jpg,png]|permit_empty',
- 'owner_email' => 'required|valid_email',
- 'type' => 'required',
- ])
- ) {
- $languageModel = new LanguageModel();
- $categoryModel = new CategoryModel();
- $data = [
- 'podcast' => $this->podcast,
- 'languages' => $languageModel->findAll(),
- 'categories' => $categoryModel->findAll(),
- ];
-
- echo view('podcast/edit', $data);
- } else {
- $this->podcast->title = $this->request->getVar('title');
- $this->podcast->name = $this->request->getVar('name');
- $this->podcast->description = $this->request->getVar('description');
- $this->podcast->episode_description_footer = $this->request->getVar(
- 'episode_description_footer'
- );
-
- $image = $this->request->getFile('image');
- if ($image->isValid()) {
- $this->podcast->image = $this->request->getFile('image');
- }
- $this->podcast->language = $this->request->getVar('language');
- $this->podcast->category = $this->request->getVar('category');
- $this->podcast->explicit =
- ($this->request->getVar('explicit') or false);
- $this->podcast->author_name = $this->request->getVar('author_name');
- $this->podcast->author_email = $this->request->getVar(
- 'author_email'
- );
- $this->podcast->owner_name = $this->request->getVar('owner_name');
- $this->podcast->owner_email = $this->request->getVar('owner_email');
- $this->podcast->type = $this->request->getVar('type');
- $this->podcast->copyright = $this->request->getVar('copyright');
- $this->podcast->block = ($this->request->getVar('block') or false);
- $this->podcast->complete =
- ($this->request->getVar('complete') or false);
- $this->podcast->custom_html_head = $this->request->getVar(
- 'custom_html_head'
- );
-
- $podcast_model = new PodcastModel();
- $podcast_model->save($this->podcast);
-
- return redirect()->to(
- base_url(route_to('podcast_view', $this->podcast->name))
- );
- }
- }
-
- public function view()
+ public function index()
{
// The page cache is set to a decade so it is deleted manually upon podcast update
$this->cachePage(DECADE);
@@ -164,14 +38,6 @@ class Podcast extends BaseController
'podcast' => $this->podcast,
'episodes' => $this->podcast->episodes,
];
- return view('podcast/view', $data);
- }
-
- public function delete()
- {
- $podcast_model = new PodcastModel();
- $podcast_model->delete($this->podcast->id);
-
- return redirect()->to(base_url(route_to('home')));
+ return view('podcast', $data);
}
}
diff --git a/app/Controllers/UnknownUserAgents.php b/app/Controllers/UnknownUserAgents.php
index 82e1a298..3eb28471 100644
--- a/app/Controllers/UnknownUserAgents.php
+++ b/app/Controllers/UnknownUserAgents.php
@@ -1,4 +1,11 @@
-forge->addField([
+ 'id' => [
+ 'type' => 'BIGINT',
+ 'constraint' => 20,
+ 'unsigned' => true,
+ 'auto_increment' => true,
+ ],
+ 'user_id' => [
+ 'type' => 'INT',
+ 'constraint' => 11,
+ 'unsigned' => true,
+ ],
+ 'podcast_id' => [
+ 'type' => 'BIGINT',
+ 'constraint' => 20,
+ 'unsigned' => true,
+ ],
+ ]);
+ $this->forge->addPrimaryKey('id');
+ $this->forge->addUniqueKey(['user_id', 'podcast_id']);
+ $this->forge->addForeignKey('user_id', 'users', 'id');
+ $this->forge->addForeignKey('podcast_id', 'podcasts', 'id');
+ $this->forge->createTable('users_podcasts');
+ }
+
+ public function down()
+ {
+ $this->forge->dropTable('users_podcasts');
+ }
+}
diff --git a/app/Database/Seeds/UserSeeder.php b/app/Database/Seeds/UserSeeder.php
new file mode 100644
index 00000000..826f1332
--- /dev/null
+++ b/app/Database/Seeds/UserSeeder.php
@@ -0,0 +1,30 @@
+ 'admin',
+ 'email' => 'admin@castopod.com',
+ 'password_hash' =>
+ // password: AGUehL3P
+ '$2y$10$TXJEHX/djW8jtzgpDVf7dOOCGo5rv1uqtAYWdwwwkttQcDkAeB2.6',
+ 'active' => 1,
+ ];
+
+ $this->db->table('users')->insert($data);
+ }
+}
diff --git a/app/Entities/Episode.php b/app/Entities/Episode.php
index 243bf3bd..20872716 100644
--- a/app/Entities/Episode.php
+++ b/app/Entities/Episode.php
@@ -135,7 +135,7 @@ class Episode extends Entity
{
return base_url(
route_to(
- 'episode_view',
+ 'episode',
$this->getPodcast()->name,
$this->attributes['slug']
)
diff --git a/app/Entities/Podcast.php b/app/Entities/Podcast.php
index 2a3407d5..386be65c 100644
--- a/app/Entities/Podcast.php
+++ b/app/Entities/Podcast.php
@@ -71,7 +71,7 @@ class Podcast extends Entity
public function getLink()
{
- return base_url(route_to('podcast_view', $this->attributes['name']));
+ return base_url(route_to('podcast', $this->attributes['name']));
}
public function getFeedUrl()
@@ -79,12 +79,25 @@ class Podcast extends Entity
return base_url(route_to('podcast_feed', $this->attributes['name']));
}
+ /**
+ * Returns the podcast's episodes
+ *
+ * @return \App\Entities\Episode[]
+ */
public function getEpisodes()
{
- $episode_model = new EpisodeModel();
+ if (empty($this->id)) {
+ throw new \RuntimeException(
+ 'Podcast must be created before getting episodes.'
+ );
+ }
- return $episode_model
- ->where('podcast_id', $this->attributes['id'])
- ->findAll();
+ if (empty($this->permissions)) {
+ $this->episodes = (new EpisodeModel())->getPodcastEpisodes(
+ $this->id
+ );
+ }
+
+ return $this->episodes;
}
}
diff --git a/app/Entities/UserPodcast.php b/app/Entities/UserPodcast.php
new file mode 100644
index 00000000..d951557d
--- /dev/null
+++ b/app/Entities/UserPodcast.php
@@ -0,0 +1,18 @@
+ 'integer',
+ 'podcast_id' => 'integer',
+ ];
+}
diff --git a/app/Helpers/analytics_helper.php b/app/Helpers/analytics_helper.php
index 73ebce65..90282c2a 100644
--- a/app/Helpers/analytics_helper.php
+++ b/app/Helpers/analytics_helper.php
@@ -13,7 +13,6 @@ function set_user_session_country()
{
$session = \Config\Services::session();
$session->start();
- $db = \Config\Database::connect();
$country = 'N/A';
diff --git a/app/Helpers/media_helper.php b/app/Helpers/media_helper.php
index 09edaf19..3a3628e6 100644
--- a/app/Helpers/media_helper.php
+++ b/app/Helpers/media_helper.php
@@ -8,7 +8,7 @@
/**
* Saves a file to the corresponding podcast folder in `public/media`
*
- * @param UploadedFile $file
+ * @param \CodeIgniter\HTTP\Files\UploadedFile $file
* @param string $podcast_name
* @param string $file_name
*
diff --git a/app/Language/en/Episode.php b/app/Language/en/Episode.php
index 8915c8de..22ff6a53 100644
--- a/app/Language/en/Episode.php
+++ b/app/Language/en/Episode.php
@@ -1,9 +1,17 @@
+/**
+ * @copyright 2020 Podlibre
+ * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
+ * @link https://castopod.org/
+ */
return [
+ 'all_podcast_episodes' => 'All podcast episodes',
+ 'create_one' => 'Add a new one',
'back_to_podcast' => 'Go back to podcast',
'edit' => 'Edit',
'delete' => 'Delete',
+ 'goto_page' => 'Go to page',
'create' => 'Add an episode',
'form' => [
'file' => 'Audio file',
diff --git a/app/Language/en/Home.php b/app/Language/en/Home.php
index 3cb0366c..435307ef 100644
--- a/app/Language/en/Home.php
+++ b/app/Language/en/Home.php
@@ -1,4 +1,9 @@
+/**
+ * @copyright 2020 Podlibre
+ * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
+ * @link https://castopod.org/
+ */
return [
'all_podcasts' => 'All podcasts',
diff --git a/app/Language/en/MyAccount.php b/app/Language/en/MyAccount.php
new file mode 100644
index 00000000..c1935bcc
--- /dev/null
+++ b/app/Language/en/MyAccount.php
@@ -0,0 +1,11 @@
+
+/**
+ * @copyright 2020 Podlibre
+ * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
+ * @link https://castopod.org/
+ */
+
+return [
+ 'passwordChangeSuccess' => 'Password has been successfully changed!',
+ 'changePassword' => 'Change my password'
+];
diff --git a/app/Language/en/Podcast.php b/app/Language/en/Podcast.php
index 776420a2..767d54e0 100644
--- a/app/Language/en/Podcast.php
+++ b/app/Language/en/Podcast.php
@@ -1,11 +1,21 @@
+/**
+ * @copyright 2020 Podlibre
+ * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
+ * @link https://castopod.org/
+ */
return [
+ 'all_podcasts' => 'All podcasts',
+ 'no_podcast' => 'No podcast found!',
+ 'create_one' => 'Add a new one',
'create' => 'Create a Podcast',
'new_episode' => 'New Episode',
'feed' => 'RSS feed',
'edit' => 'Edit',
'delete' => 'Delete',
+ 'see_episodes' => 'See episodes',
+ 'goto_page' => 'Go to page',
'form' => [
'title' => 'Title',
'name' => 'Name',
diff --git a/app/Language/en/User.php b/app/Language/en/User.php
new file mode 100644
index 00000000..fcf9134c
--- /dev/null
+++ b/app/Language/en/User.php
@@ -0,0 +1,28 @@
+
+/**
+ * @copyright 2020 Podlibre
+ * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
+ * @link https://castopod.org/
+ */
+
+return [
+ 'createSuccess' => 'User created successfully! The new user will be prompted with a password reset during his first login attempt.',
+ 'forcePassResetSuccess' => 'The user will be prompted with a password reset during his next login attempt.',
+ 'banSuccess' => 'User has been banned.',
+ 'unbanSuccess' => 'User has been unbanned.',
+ 'forcePassReset' => 'Force pass reset',
+ 'ban' => 'Ban',
+ 'unban' => 'Unban',
+ 'delete' => 'Delete',
+ 'create' => 'Create a user',
+ 'form' => [
+ 'email' => 'Email',
+ 'username' => 'Username',
+ 'password' => 'Password',
+ 'new_password' => 'New Password',
+ 'repeat_password' => 'Repeat password',
+ 'repeat_new_password' => 'Repeat new password',
+ 'submit_create' => 'Create user',
+ 'submit_edit' => 'Save',
+ ]
+];
diff --git a/app/Models/EpisodeModel.php b/app/Models/EpisodeModel.php
index bd29dbf6..ed2ea4e9 100644
--- a/app/Models/EpisodeModel.php
+++ b/app/Models/EpisodeModel.php
@@ -61,11 +61,30 @@ class EpisodeModel extends Model
is_array($data['id']) ? $data['id'][0] : $data['id']
);
- $cache = \Config\Services::cache();
-
// delete cache for rss feed, podcast and episode pages
- $cache->delete(md5($episode->podcast->feed_url));
- $cache->delete(md5($episode->podcast->link));
- $cache->delete(md5($episode->link));
+ cache()->delete(md5($episode->podcast->feed_url));
+ cache()->delete(md5($episode->podcast->link));
+ cache()->delete(md5($episode->link));
+
+ // delete model requests cache
+ cache()->delete("{$episode->podcast_id}_episodes");
+ }
+
+ /**
+ * Gets all episodes for a podcast
+ *
+ * @param int $podcastId
+ *
+ * @return \App\Entities\Episode[]
+ */
+ public function getPodcastEpisodes(int $podcastId): array
+ {
+ if (!($found = cache("{$podcastId}_episodes"))) {
+ $found = $this->where('podcast_id', $podcastId)->findAll();
+
+ cache()->save("{$podcastId}_episodes", $found, 300);
+ }
+
+ return $found;
}
}
diff --git a/app/Models/PodcastModel.php b/app/Models/PodcastModel.php
index fba25f5c..09712b6a 100644
--- a/app/Models/PodcastModel.php
+++ b/app/Models/PodcastModel.php
@@ -49,11 +49,9 @@ class PodcastModel extends Model
is_array($data['id']) ? $data['id'][0] : $data['id']
);
- $cache = \Config\Services::cache();
-
// delete cache for rss feed and podcast pages
- $cache->delete(md5($podcast->feed_url));
- $cache->delete(md5($podcast->link));
+ cache()->delete(md5($podcast->feed_url));
+ cache()->delete(md5($podcast->link));
// TODO: clear cache for every podcast's episode page?
// foreach ($podcast->episodes as $episode) {
// $cache->delete(md5($episode->link));
diff --git a/app/Models/UserPodcastModel.php b/app/Models/UserPodcastModel.php
new file mode 100644
index 00000000..32d36b05
--- /dev/null
+++ b/app/Models/UserPodcastModel.php
@@ -0,0 +1,23 @@
+
= $this->renderSection('content') ?>
- Powered by Castopod , a Podlibre initiative.
+ Powered by Castopod , a Podlibre initiative.
-
+
+
+ = view('admin/_sidenav') ?>
+
+
+ = view('_message_block') ?>
+
+ = $this->renderSection('content') ?>
+
+
+
+ Powered by Castopod , a Podlibre initiative.
+
+
+
+
+
+ = view('_message_block') ?>
+
+ = $this->renderSection('content') ?>
+
+
+ = $this->renderSection('footer') ?>
+
+ Powered by Castopod , a Podlibre initiative.
+
+
+
\ No newline at end of file
+
diff --git a/app/Views/_message_block.php b/app/Views/_message_block.php
new file mode 100644
index 00000000..466b5780
--- /dev/null
+++ b/app/Views/_message_block.php
@@ -0,0 +1,20 @@
+has('message')): ?>
+
+
diff --git a/app/Views/admin/_layout.php b/app/Views/admin/_layout.php
new file mode 100644
index 00000000..a96b0a7c
--- /dev/null
+++ b/app/Views/admin/_layout.php
@@ -0,0 +1,37 @@
+
+
+
+
diff --git a/app/Views/admin/_sidenav.php b/app/Views/admin/_sidenav.php
new file mode 100644
index 00000000..f68ae5ba
--- /dev/null
+++ b/app/Views/admin/_sidenav.php
@@ -0,0 +1,54 @@
+
diff --git a/app/Views/admin/dashboard.php b/app/Views/admin/dashboard.php
new file mode 100644
index 00000000..910003fa
--- /dev/null
+++ b/app/Views/admin/dashboard.php
@@ -0,0 +1,8 @@
+= $this->extend('admin/_layout') ?>
+
+= $this->section('content') ?>
+
+
+
+= $this->endSection() ?>
+
diff --git a/app/Views/episode/create.php b/app/Views/admin/episode/create.php
similarity index 98%
rename from app/Views/episode/create.php
rename to app/Views/admin/episode/create.php
index 60647708..af1e9d40 100644
--- a/app/Views/episode/create.php
+++ b/app/Views/admin/episode/create.php
@@ -1,4 +1,4 @@
-= $this->extend('layouts/default') ?>
+= $this->extend('admin/_layout') ?>
= $this->section('content') ?>
diff --git a/app/Views/episode/edit.php b/app/Views/admin/episode/edit.php
similarity index 99%
rename from app/Views/episode/edit.php
rename to app/Views/admin/episode/edit.php
index 4ef949f6..51b91d98 100644
--- a/app/Views/episode/edit.php
+++ b/app/Views/admin/episode/edit.php
@@ -1,4 +1,4 @@
-= $this->extend('layouts/default') ?>
+= $this->extend('admin/_layout') ?>
= $this->section('content') ?>
diff --git a/app/Views/admin/episode/list.php b/app/Views/admin/episode/list.php
new file mode 100644
index 00000000..81d4c1f9
--- /dev/null
+++ b/app/Views/admin/episode/list.php
@@ -0,0 +1,65 @@
+= $this->extend('admin/_layout') ?>
+
+= $this->section('content') ?>
+
+
+
+= $this->endSection()
+?>
diff --git a/app/Views/admin/my_account/change_password.php b/app/Views/admin/my_account/change_password.php
new file mode 100644
index 00000000..28cc35ea
--- /dev/null
+++ b/app/Views/admin/my_account/change_password.php
@@ -0,0 +1,31 @@
+= $this->extend('admin/_layout') ?>
+
+= $this->section('content') ?>
+
+
+
+= $this->endSection()
+?>
diff --git a/app/Views/admin/my_account/view.php b/app/Views/admin/my_account/view.php
new file mode 100644
index 00000000..03deb336
--- /dev/null
+++ b/app/Views/admin/my_account/view.php
@@ -0,0 +1,31 @@
+= $this->extend('admin/_layout') ?>
+
+= $this->section('content') ?>
+
+
+
+= $this->endSection()
+?>
diff --git a/app/Views/podcast/create.php b/app/Views/admin/podcast/create.php
similarity index 99%
rename from app/Views/podcast/create.php
rename to app/Views/admin/podcast/create.php
index 44ab5ed7..c9eed74e 100644
--- a/app/Views/podcast/create.php
+++ b/app/Views/admin/podcast/create.php
@@ -1,4 +1,4 @@
-= $this->extend('layouts/default') ?>
+= $this->extend('admin/_layout') ?>
= $this->section('content') ?>
diff --git a/app/Views/podcast/edit.php b/app/Views/admin/podcast/edit.php
similarity index 99%
rename from app/Views/podcast/edit.php
rename to app/Views/admin/podcast/edit.php
index 4c83baf0..4dcfff66 100644
--- a/app/Views/podcast/edit.php
+++ b/app/Views/admin/podcast/edit.php
@@ -1,4 +1,4 @@
-= $this->extend('layouts/default') ?>
+= $this->extend('admin/_layout') ?>
= $this->section('content') ?>
diff --git a/app/Views/admin/podcast/list.php b/app/Views/admin/podcast/list.php
new file mode 100644
index 00000000..7510eeef
--- /dev/null
+++ b/app/Views/admin/podcast/list.php
@@ -0,0 +1,49 @@
+= $this->extend('admin/_layout') ?>
+
+= $this->section('content') ?>
+
+
+
+= $this->endSection()
+?>
diff --git a/app/Views/admin/user/create.php b/app/Views/admin/user/create.php
new file mode 100644
index 00000000..82bfe99d
--- /dev/null
+++ b/app/Views/admin/user/create.php
@@ -0,0 +1,38 @@
+= $this->extend('admin/_layout') ?>
+
+= $this->section('content') ?>
+
+
+
+= $this->endSection()
+?>
diff --git a/app/Views/admin/user/list.php b/app/Views/admin/user/list.php
new file mode 100644
index 00000000..a0d12121
--- /dev/null
+++ b/app/Views/admin/user/list.php
@@ -0,0 +1,61 @@
+= $this->extend('admin/_layout') ?>
+
+= $this->section('content') ?>
+
+
+
+= $this->endSection()
+?>
diff --git a/app/Views/auth/_layout.php b/app/Views/auth/_layout.php
new file mode 100644
index 00000000..24d4aff6
--- /dev/null
+++ b/app/Views/auth/_layout.php
@@ -0,0 +1,31 @@
+
+
+
+
diff --git a/app/Views/auth/change_password.php b/app/Views/auth/change_password.php
new file mode 100644
index 00000000..3ca545b6
--- /dev/null
+++ b/app/Views/auth/change_password.php
@@ -0,0 +1,29 @@
+= $this->extend($config->viewLayout) ?>
+
+= $this->section('title') ?>
+ = lang('Auth.resetYourPassword') ?>
+= $this->endSection() ?>
+
+
+= $this->section('content') ?>
+
+
+
+= $this->endSection() ?>
diff --git a/app/Views/auth/emails/activation.php b/app/Views/auth/emails/activation.php
new file mode 100644
index 00000000..d76eb17f
--- /dev/null
+++ b/app/Views/auth/emails/activation.php
@@ -0,0 +1,11 @@
+
This is activation email for your account on = base_url() ?>.
To activate your account use this URL.
If you did not registered on this website, you can safely ignore this email.
\ No newline at end of file
diff --git a/app/Views/auth/emails/forgot.php b/app/Views/auth/emails/forgot.php
new file mode 100644
index 00000000..f6509c83
--- /dev/null
+++ b/app/Views/auth/emails/forgot.php
@@ -0,0 +1,13 @@
+
Someone requested a password reset at this email address for = base_url() ?>.
To reset the password use this code or URL and follow the instructions.
If you did not request a password reset, you can safely ignore this email.
diff --git a/app/Views/auth/forgot.php b/app/Views/auth/forgot.php
new file mode 100644
index 00000000..cef9a088
--- /dev/null
+++ b/app/Views/auth/forgot.php
@@ -0,0 +1,25 @@
+= $this->extend($config->viewLayout) ?>
+
+= $this->section('title') ?>
+ = lang('Auth.forgotPassword') ?>
+= $this->endSection() ?>
+
+
+= $this->section('content') ?>
+
+
+
+= $this->endSection() ?>
diff --git a/app/Views/auth/login.php b/app/Views/auth/login.php
new file mode 100644
index 00000000..2d7fcee1
--- /dev/null
+++ b/app/Views/auth/login.php
@@ -0,0 +1,44 @@
+= $this->extend($config->viewLayout) ?>
+
+= $this->section('title') ?>
+ = lang('Auth.loginTitle') ?>
+= $this->endSection() ?>
+
+
+= $this->section('content') ?>
+
+
+
+= $this->endSection() ?>
diff --git a/app/Views/auth/register.php b/app/Views/auth/register.php
new file mode 100644
index 00000000..571aff38
--- /dev/null
+++ b/app/Views/auth/register.php
@@ -0,0 +1,54 @@
+= $this->extend($config->viewLayout) ?>
+
+= $this->section('title') ?>
+ = lang('Auth.register') ?>
+= $this->endSection() ?>
+
+
+= $this->section('content') ?>
+
+
+
+= $this->endSection() ?>
diff --git a/app/Views/auth/reset.php b/app/Views/auth/reset.php
new file mode 100644
index 00000000..d2bfbce9
--- /dev/null
+++ b/app/Views/auth/reset.php
@@ -0,0 +1,38 @@
+= $this->extend($config->viewLayout) ?>
+
+= $this->section('title') ?>
+ = lang('Auth.resetYourPassword') ?>
+= $this->endSection() ?>
+
+
+= $this->section('content') ?>
+
+
+
+= $this->endSection() ?>
diff --git a/app/Views/episode/view.php b/app/Views/episode.php
similarity index 51%
rename from app/Views/episode/view.php
rename to app/Views/episode.php
index f0732d64..aea7ddd9 100644
--- a/app/Views/episode/view.php
+++ b/app/Views/episode.php
@@ -1,9 +1,9 @@
-= $this->extend('layouts/default') ?>
+= $this->extend('_layout') ?>
= $this->section('content') ?>
< = lang('Episode.back_to_podcast') ?>