diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md index 612c3284a3321604c25b727c88f5516ce54e72cc..fed484c99b569c3f8378e09f913a8ddc31b05ae3 100644 --- a/DEPENDENCIES.md +++ b/DEPENDENCIES.md @@ -5,13 +5,13 @@ Castopod uses the following components: PHP Dependencies: - [Code Igniter 4](https://codeigniter.com) ([MIT License](https://codeigniter.com/user_guide/license.html)) -- [User agent list](https://github.com/opawg/user-agents) ([by Open Podcast Analytics Working Group](https://github.com/opawg)) ([MIT license](https://github.com/opawg/user-agents/blob/master/LICENSE)) - [WhichBrowser/Parser-PHP](https://github.com/WhichBrowser/Parser-PHP) ([MIT License](https://github.com/WhichBrowser/Parser-PHP/blob/master/LICENSE)) - [GeoIP2 PHP API](https://github.com/maxmind/GeoIP2-php) ([Apache License 2.0](https://github.com/maxmind/GeoIP2-php/blob/master/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)) - [commonmark](https://commonmark.thephpleague.com/) ([BSD 3-Clause "New" or "Revised" License](https://github.com/thephpleague/commonmark/blob/latest/LICENSE)) - [phpdotenv](https://github.com/vlucas/phpdotenv) ([ BSD-3-Clause License ](https://github.com/vlucas/phpdotenv/blob/master/LICENSE)) +- [HTML To Markdown for PHP](https://github.com/thephpleague/html-to-markdown) ([MIT License](https://github.com/thephpleague/html-to-markdown/blob/master/LICENSE)) Javascript dependencies: @@ -24,3 +24,4 @@ Javascript dependencies: Other: - [RemixIcon](https://remixicon.com/) ([Apache License 2.0](https://github.com/Remix-Design/RemixIcon/blob/master/License)) +- [User agent list](https://github.com/opawg/user-agents) ([by Open Podcast Analytics Working Group](https://github.com/opawg)) ([MIT license](https://github.com/opawg/user-agents/blob/master/LICENSE)) diff --git a/app/Config/Routes.php b/app/Config/Routes.php index adec234ceb8f5162a65cddd55a2291e4bc566a60..be223070b6ce73a416b6d215c7d82a89d3f76235 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -71,6 +71,10 @@ $routes->group( 'as' => 'admin', ]); + $routes->get('my-podcasts', 'Podcast::myPodcasts', [ + 'as' => 'my-podcasts', + ]); + // Podcasts $routes->group('podcasts', function ($routes) { $routes->get('/', 'Podcast::list', [ @@ -83,6 +87,13 @@ $routes->group( $routes->post('new', 'Podcast::attemptCreate', [ 'filter' => 'permission:podcasts-create', ]); + $routes->get('import', 'Podcast::import', [ + 'as' => 'podcast-import', + 'filter' => 'permission:podcasts-import', + ]); + $routes->post('import', 'Podcast::attemptImport', [ + 'filter' => 'permission:podcasts-import', + ]); // Podcast // Use ids in admin area to help permission and group lookups diff --git a/app/Controllers/Admin/Podcast.php b/app/Controllers/Admin/Podcast.php index 6de23cf9c8c07c16a6ac2810e24636e0c18c40ac..adfdeaac1970cfac230c946bbf9a1f92b03890ec 100644 --- a/app/Controllers/Admin/Podcast.php +++ b/app/Controllers/Admin/Podcast.php @@ -11,7 +11,9 @@ namespace App\Controllers\Admin; use App\Models\CategoryModel; use App\Models\LanguageModel; use App\Models\PodcastModel; +use App\Models\EpisodeModel; use Config\Services; +use League\HTMLToMarkdown\HtmlConverter; class Podcast extends BaseController { @@ -69,7 +71,7 @@ class Podcast extends BaseController $categoryOptions = array_reduce( $categories, function ($result, $category) { - $result[$category->code] = lang( + $result[$category->id] = lang( 'Podcast.category_options.' . $category->code ); return $result; @@ -110,7 +112,7 @@ class Podcast extends BaseController ), 'image' => $this->request->getFile('image'), 'language' => $this->request->getPost('language'), - 'category' => $this->request->getPost('category'), + 'category_id' => $this->request->getPost('category'), 'explicit' => $this->request->getPost('explicit') == 'yes', 'author' => $this->request->getPost('author'), 'owner_name' => $this->request->getPost('owner_name'), @@ -151,6 +153,222 @@ class Podcast extends BaseController return redirect()->route('podcast-view', [$newPodcastId]); } + public function import() + { + helper(['form', 'misc']); + + $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->id] = lang( + 'Podcast.category_options.' . $category->code + ); + return $result; + }, + [] + ); + + $data = [ + 'languageOptions' => $languageOptions, + 'categoryOptions' => $categoryOptions, + 'browserLang' => get_browser_language( + $this->request->getServer('HTTP_ACCEPT_LANGUAGE') + ), + ]; + + return view('admin/podcast/import', $data); + } + + public function attemptImport() + { + helper(['media', 'misc']); + + $rules = [ + 'name' => 'required', + 'imported_feed_url' => 'required', + ]; + + if (!$this->validate($rules)) { + return redirect() + ->back() + ->withInput() + ->with('errors', $this->validator->getErrors()); + } + try { + $feed = simplexml_load_file( + $this->request->getPost('imported_feed_url') + ); + } catch (\ErrorException $ex) { + return redirect() + ->back() + ->withInput() + ->with('errors', [ + $ex->getMessage() . + ': <a href="' . + $this->request->getPost('imported_feed_url') . + '" rel="noreferrer noopener" target="_blank">' . + $this->request->getPost('imported_feed_url') . + ' ⎋</a>', + ]); + } + + $nsItunes = $feed->channel[0]->children( + 'http://www.itunes.com/dtds/podcast-1.0.dtd' + ); + + $podcast = new \App\Entities\Podcast([ + 'name' => $this->request->getPost('name'), + 'imported_feed_url' => $this->request->getPost('imported_feed_url'), + + 'title' => $feed->channel[0]->title, + 'description' => $feed->channel[0]->description, + 'image' => download_file($nsItunes->image->attributes()), + 'language' => $this->request->getPost('language'), + 'category_id' => $this->request->getPost('category'), + 'explicit' => empty($nsItunes->explicit) + ? false + : $nsItunes->explicit == 'yes', + 'author' => $nsItunes->author, + 'owner_name' => $nsItunes->owner->name, + 'owner_email' => $nsItunes->owner->email, + 'type' => empty($nsItunes->type) ? 'episodic' : $nsItunes->type, + 'copyright' => $feed->channel[0]->copyright, + 'block' => empty($nsItunes->block) + ? false + : $nsItunes->block == 'yes', + 'complete' => empty($nsItunes->complete) + ? false + : $nsItunes->complete == 'yes', + 'episode_description_footer' => '', + 'custom_html_head' => '', + 'created_by' => user(), + 'updated_by' => user(), + ]); + + $podcastModel = new PodcastModel(); + $db = \Config\Database::connect(); + + $db->transStart(); + + if (!($newPodcastId = $podcastModel->insert($podcast, true))) { + $db->transComplete(); + return redirect() + ->back() + ->withInput() + ->with('errors', $podcastModel->errors()); + } + + $authorize = Services::authorization(); + $podcastAdminGroup = $authorize->group('podcast_admin'); + + $podcastModel->addPodcastContributor( + user()->id, + $newPodcastId, + $podcastAdminGroup->id + ); + + $converter = new HtmlConverter(); + + $numberItems = $feed->channel[0]->item->count(); + $lastItem = + !empty($this->request->getPost('max_episodes')) && + $this->request->getPost('max_episodes') < $numberItems + ? $this->request->getPost('max_episodes') + : $numberItems; + + $slugs = []; + + // For each Episode: + for ($itemNumber = 1; $itemNumber <= $lastItem; $itemNumber++) { + $item = $feed->channel[0]->item[$numberItems - $itemNumber]; + + $nsItunes = $item->children( + 'http://www.itunes.com/dtds/podcast-1.0.dtd' + ); + + $slug = slugify( + $this->request->getPost('slug_field') == 'title' + ? $item->title + : basename($item->link) + ); + if (in_array($slug, $slugs)) { + $slugNumber = 2; + while (in_array($slug . '-' . $slugNumber, $slugs)) { + $slugNumber++; + } + $slug = $slug . '-' . $slugNumber; + } + $slugs[] = $slug; + + $newEpisode = new \App\Entities\Episode([ + 'podcast_id' => $newPodcastId, + 'guid' => empty($item->guid) ? null : $item->guid, + 'title' => $item->title, + 'slug' => $slug, + 'enclosure' => download_file($item->enclosure->attributes()), + 'description' => $converter->convert( + $this->request->getPost('description_field') == 'summary' + ? $nsItunes->summary + : ($this->request->getPost('description_field') == + 'subtitle_summary' + ? '<h3>' . + $nsItunes->subtitle . + "</h3>\n" . + $nsItunes->summary + : $item->description) + ), + 'image' => empty($nsItunes->image->attributes()) + ? null + : download_file($nsItunes->image->attributes()), + 'explicit' => $nsItunes->explicit == 'yes', + 'number' => $this->request->getPost('force_renumber') + ? $itemNumber + : $nsItunes->episode, + 'season_number' => empty( + $this->request->getPost('season_number') + ) + ? $nsItunes->season + : $this->request->getPost('season_number'), + 'type' => empty($nsItunes->episodeType) + ? 'full' + : $nsItunes->episodeType, + 'block' => empty($nsItunes->block) + ? false + : $nsItunes->block == 'yes', + 'created_by' => user(), + 'updated_by' => user(), + ]); + $newEpisode->setPublishedAt( + date('Y-m-d', strtotime($item->pubDate)), + date('H:i:s', strtotime($item->pubDate)) + ); + + $episodeModel = new EpisodeModel(); + + if (!$episodeModel->save($newEpisode)) { + // FIX: What shall we do? + return redirect() + ->back() + ->withInput() + ->with('errors', $episodeModel->errors()); + } + } + + $db->transComplete(); + + return redirect()->route('podcast-list'); + } + public function edit() { helper('form'); @@ -168,7 +386,7 @@ class Podcast extends BaseController $categoryOptions = array_reduce( $categories, function ($result, $category) { - $result[$category->code] = lang( + $result[$category->id] = lang( 'Podcast.category_options.' . $category->code ); return $result; @@ -212,7 +430,7 @@ class Podcast extends BaseController $this->podcast->image = $image; } $this->podcast->language = $this->request->getPost('language'); - $this->podcast->category = $this->request->getPost('category'); + $this->podcast->category_id = $this->request->getPost('category'); $this->podcast->explicit = $this->request->getPost('explicit') == 'yes'; $this->podcast->author = $this->request->getPost('author'); $this->podcast->owner_name = $this->request->getPost('owner_name'); 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 5aa7aa20c45cb2df3e0a91f3555b6b37f55e72e5..4c024439dc7e9cfc32671814f28917bae561f2fa 100644 --- a/app/Database/Migrations/2020-05-30-101500_add_podcasts.php +++ b/app/Database/Migrations/2020-05-30-101500_add_podcasts.php @@ -44,10 +44,11 @@ class AddPodcasts extends Migration 'type' => 'VARCHAR', 'constraint' => 2, ], - 'category' => [ - 'type' => 'VARCHAR', - 'constraint' => 1024, - 'null' => true, + 'category_id' => [ + 'type' => 'INT', + 'constraint' => 10, + 'unsigned' => true, + 'default' => 0, ], 'explicit' => [ 'type' => 'TINYINT', @@ -105,6 +106,13 @@ class AddPodcasts extends Migration 'constraint' => 11, 'unsigned' => true, ], + 'imported_feed_url' => [ + 'type' => 'VARCHAR', + 'constraint' => 1024, + 'comment' => + 'The RSS feed URL if this podcast was imported, NULL otherwise.', + 'null' => true, + ], 'created_at' => [ 'type' => 'TIMESTAMP', ], @@ -117,6 +125,7 @@ class AddPodcasts extends Migration ], ]); $this->forge->addKey('id', true); + $this->forge->addForeignKey('category_id', 'categories', '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 c0fa74af15843609c1a2cae754107d1364b00730..49625900ba874cdd91742938b0080c6e26e1c285 100644 --- a/app/Database/Migrations/2020-06-05-170000_add_episodes.php +++ b/app/Database/Migrations/2020-06-05-170000_add_episodes.php @@ -29,6 +29,10 @@ class AddEpisodes extends Migration 'constraint' => 20, 'unsigned' => true, ], + 'guid' => [ + 'type' => 'VARCHAR', + 'constraint' => 191, + ], 'title' => [ 'type' => 'VARCHAR', 'constraint' => 1024, @@ -59,16 +63,17 @@ class AddEpisodes extends Migration 'type' => 'INT', 'constraint' => 10, 'unsigned' => true, + 'null' => true, ], 'season_number' => [ 'type' => 'INT', 'constraint' => 10, 'unsigned' => true, - 'default' => 1, + 'null' => true, ], 'type' => [ 'type' => 'ENUM', - 'constraint' => ['full', 'trailer', 'bonus'], + 'constraint' => ['trailer', 'full', 'bonus'], 'default' => 'full', ], 'block' => [ @@ -103,8 +108,6 @@ class AddEpisodes extends Migration ]); $this->forge->addKey('id', true); $this->forge->addUniqueKey(['podcast_id', 'slug']); - - $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'); 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 c65929d09812fb7ee1d41ec6e8dfbc78c3306b9b..c012efd6b1f7e69f716fc205b339718532e4601e 100644 --- a/app/Database/Migrations/2020-06-05-190000_add_platforms.php +++ b/app/Database/Migrations/2020-06-05-190000_add_platforms.php @@ -66,6 +66,8 @@ class AddPlatforms extends Migration 'type' => 'TINYINT', 'constraint' => 1, 'default' => 0, + 'comment' => + 'Android deeplinking for this platform: 0=No, 1=Manual, 2=Automatic.', ], 'logo_file_name' => [ 'type' => 'VARCHAR', diff --git a/app/Database/Seeds/AuthSeeder.php b/app/Database/Seeds/AuthSeeder.php index 6ddfd950128b5fff43f9e08992afb6be8408ddcb..06286125e8b03ef3aca0f61cb9c3530da0d4d455 100644 --- a/app/Database/Seeds/AuthSeeder.php +++ b/app/Database/Seeds/AuthSeeder.php @@ -90,6 +90,11 @@ class AuthSeeder extends Seeder 'description' => 'Add a new podcast', 'has_permission' => ['superadmin'], ], + [ + 'name' => 'import', + 'description' => 'Import a new podcast from an external feed', + 'has_permission' => ['superadmin'], + ], [ 'name' => 'list', 'description' => 'List all podcasts and their episodes', diff --git a/app/Database/Seeds/PlatformSeeder.php b/app/Database/Seeds/PlatformSeeder.php index fd02b74396c030ec396bc85993c6381e3bbd815a..c46a0e62f41a35c8e89bd29279204d73a19c6789 100644 --- a/app/Database/Seeds/PlatformSeeder.php +++ b/app/Database/Seeds/PlatformSeeder.php @@ -209,6 +209,19 @@ class PlatformSeeder extends Seeder 'android_deeplink' => 2, 'logo_file_name' => 'Podbean.png', ], + [ + 'name' => 'Podcast Addict', + 'home_url' => 'https://podcastaddict.com/', + 'submit_url' => 'https://podcastaddict.com/submit', + 'iosapp_url' => '', + 'androidapp_url' => + 'https://play.google.com/store/apps/details?id=com.bambuna.podcastaddict', + 'comment' => '', + 'display_by_default' => 0, + 'ios_deeplink' => 0, + 'android_deeplink' => 2, + 'logo_file_name' => 'podcastaddict.svg', + ], [ 'name' => 'Podcastland', 'home_url' => 'https://podcastland.com/', diff --git a/app/Entities/Episode.php b/app/Entities/Episode.php index 45f6bae67560d11c925e2abe1f5d940188bb6c5c..1ddc42ee390a5c43c1bd62c13f2e689ccc2f2d02 100644 --- a/app/Entities/Episode.php +++ b/app/Entities/Episode.php @@ -19,11 +19,6 @@ class Episode extends Entity */ protected $podcast; - /** - * @var string - */ - protected $GUID; - /** * @var string */ @@ -77,13 +72,14 @@ class Episode extends Entity ]; protected $casts = [ + 'guid' => 'string', 'slug' => 'string', 'title' => 'string', 'enclosure_uri' => 'string', 'description' => 'string', 'image_uri' => '?string', 'explicit' => 'boolean', - 'number' => 'integer', + 'number' => '?integer', 'season_number' => '?integer', 'type' => 'string', 'block' => 'boolean', @@ -91,9 +87,19 @@ class Episode extends Entity 'updated_by' => 'integer', ]; - public function setImage(?\CodeIgniter\HTTP\Files\UploadedFile $image) + /** + * Saves an episode image + * + * @param \CodeIgniter\HTTP\Files\UploadedFile|\CodeIgniter\Files\File $image + * + */ + public function setImage($image) { - if (!empty($image) && $image->isValid()) { + if ( + !empty($image) && + (!($image instanceof \CodeIgniter\HTTP\Files\UploadedFile) || + $image->isValid()) + ) { // check whether the user has inputted an image and store it $this->attributes['image_uri'] = save_podcast_media( $image, @@ -136,10 +142,19 @@ class Episode extends Entity return $this->getPodcast()->image_url; } - public function setEnclosure( - \CodeIgniter\HTTP\Files\UploadedFile $enclosure = null - ) { - if (!empty($enclosure) && $enclosure->isValid()) { + /** + * Saves an enclosure + * + * @param \CodeIgniter\HTTP\Files\UploadedFile|\CodeIgniter\Files\File $enclosure + * + */ + public function setEnclosure($enclosure = null) + { + if ( + !empty($enclosure) && + (!($enclosure instanceof \CodeIgniter\HTTP\Files\UploadedFile) || + $enclosure->isValid()) + ) { helper('media'); $this->attributes['enclosure_uri'] = save_podcast_media( @@ -194,9 +209,11 @@ class Episode extends Entity ); } - public function getGUID() + public function setGuid($guid = null) { - return $this->getLink(); + return $this->attributes['guid'] = empty($guid) + ? $this->getLink() + : $guid; } public function getPodcast() diff --git a/app/Entities/Podcast.php b/app/Entities/Podcast.php index c139ef028e1147c486639ac02b93cfa52dbc716d..916d7232140b9410a89e5f477c26f9ebd0aec977 100644 --- a/app/Entities/Podcast.php +++ b/app/Entities/Podcast.php @@ -57,7 +57,7 @@ class Podcast extends Entity 'description' => 'string', 'image_uri' => 'string', 'language' => 'string', - 'category' => 'string', + 'category_id' => 'integer', 'explicit' => 'boolean', 'author' => '?string', 'owner_name' => '?string', @@ -70,9 +70,16 @@ class Podcast extends Entity 'custom_html_head' => '?string', 'created_by' => 'integer', 'updated_by' => 'integer', + 'imported_feed_url' => '?string', ]; - public function setImage(\CodeIgniter\HTTP\Files\UploadedFile $image = null) + /** + * Saves a cover image to the corresponding podcast folder in `public/media/podcast_name/` + * + * @param \CodeIgniter\HTTP\Files\UploadedFile|\CodeIgniter\Files\File $image + * + */ + public function setImage($image = null) { if ($image) { helper('media'); diff --git a/app/Helpers/media_helper.php b/app/Helpers/media_helper.php index 1f21679a84258750214cd2e83958ab4b7b91dd91..594bb009dfef5e18e6cbb2ce764c05a587a5b61b 100644 --- a/app/Helpers/media_helper.php +++ b/app/Helpers/media_helper.php @@ -9,7 +9,7 @@ /** * Saves a file to the corresponding podcast folder in `public/media` * - * @param \CodeIgniter\HTTP\Files\UploadedFile $file + * @param \CodeIgniter\HTTP\Files\UploadedFile|\CodeIgniter\Files\File $file * @param string $podcast_name * @param string $file_name * @@ -17,7 +17,12 @@ */ function save_podcast_media($file, $podcast_name, $media_name) { - $file_name = $media_name . '.' . $file->guessExtension(); + $file_name = $media_name . '.' . $file->getExtension(); + + if (!file_exists(config('App')->mediaRoot . '/' . $podcast_name)) { + mkdir(config('App')->mediaRoot . '/' . $podcast_name, 0777, true); + touch(config('App')->mediaRoot . '/' . $podcast_name . '/index.html'); + } // move to media folder and overwrite file if already existing $file->move( @@ -29,6 +34,20 @@ function save_podcast_media($file, $podcast_name, $media_name) return $podcast_name . '/' . $file_name; } +function download_file($fileUrl) +{ + $tmpFilename = + time() . + '_' . + bin2hex(random_bytes(10)) . + '.' . + pathinfo($fileUrl, PATHINFO_EXTENSION); + $tmpFilePath = WRITEPATH . 'uploads/' . $tmpFilename; + file_put_contents($tmpFilePath, file_get_contents($fileUrl)); + + return new \CodeIgniter\Files\File($tmpFilePath); +} + /** * Prefixes the root media path to a given uri * diff --git a/app/Helpers/misc_helper.php b/app/Helpers/misc_helper.php index b1511ef39a81d183329b70147495cbe9f272978d..b1d1b168a34a99922dd040ae3e254ee1d60aa4a4 100644 --- a/app/Helpers/misc_helper.php +++ b/app/Helpers/misc_helper.php @@ -35,3 +35,111 @@ function startsWith($string, $query) { return substr($string, 0, strlen($query)) === $query; } + +function slugify($text) +{ + if (empty($text)) { + return 'n-a'; + } + + // replace non letter or digits by - + $text = preg_replace('~[^\pL\d]+~u', '-', $text); + + $unwanted_array = [ + 'Å ' => 'S', + 'Å¡' => 's', + 'Ä' => 'Dj', + 'Ä‘' => 'dj', + 'Ž' => 'Z', + 'ž' => 'z', + 'ÄŒ' => 'C', + 'Ä' => 'c', + 'Ć' => 'C', + 'ć' => 'c', + 'À' => 'A', + 'Ã' => 'A', + 'Â' => 'A', + 'Ã' => 'A', + 'Ä' => 'A', + 'Ã…' => 'A', + 'Æ' => 'AE', + 'Ç' => 'C', + 'È' => 'E', + 'É' => 'E', + 'Ê' => 'E', + 'Ë' => 'E', + 'ÃŒ' => 'I', + 'Ã' => 'I', + 'ÃŽ' => 'I', + 'Ã' => 'I', + 'Ñ' => 'N', + 'Ã’' => 'O', + 'Ó' => 'O', + 'Ô' => 'O', + 'Õ' => 'O', + 'Ö' => 'O', + 'Ø' => 'O', + 'Å’' => 'OE', + 'Ù' => 'U', + 'Ú' => 'U', + 'Û' => 'U', + 'Ãœ' => 'U', + 'Ã' => 'Y', + 'Þ' => 'B', + 'ß' => 'Ss', + 'à ' => 'a', + 'á' => 'a', + 'â' => 'a', + 'ã' => 'a', + 'ä' => 'a', + 'Ã¥' => 'a', + 'æ' => 'ae', + 'ç' => 'c', + 'è' => 'e', + 'é' => 'e', + 'ê' => 'e', + 'ë' => 'e', + 'ì' => 'i', + 'Ã' => 'i', + 'î' => 'i', + 'ï' => 'i', + 'ð' => 'o', + 'ñ' => 'n', + 'ò' => 'o', + 'ó' => 'o', + 'ô' => 'o', + 'õ' => 'o', + 'ö' => 'o', + 'ø' => 'o', + 'Å“' => 'OE', + 'ù' => 'u', + 'ú' => 'u', + 'û' => 'u', + 'ý' => 'y', + 'ý' => 'y', + 'þ' => 'b', + 'ÿ' => 'y', + 'Å”' => 'R', + 'Å•' => 'r', + '/' => '-', + ' ' => '-', + ]; + $text = strtr($text, $unwanted_array); + + // transliterate + $text = iconv('utf-8', 'us-ascii//TRANSLIT', $text); + + // remove unwanted characters + $text = preg_replace('~[^-\w]+~', '', $text); + + // trim + $text = trim($text, '-'); + + // remove duplicate - + $text = preg_replace('~-+~', '-', $text); + + // lowercase + $text = strtolower($text); + + return $text; +} diff --git a/app/Helpers/rss_helper.php b/app/Helpers/rss_helper.php index 58f5072628ff45e2ec6cb03f61512f4f229f035a..f08297f698de6d8ed006f6e731d61804f17a1240 100644 --- a/app/Helpers/rss_helper.php +++ b/app/Helpers/rss_helper.php @@ -23,7 +23,7 @@ function get_rss_feed($podcast) $episodes = $podcast->episodes; $podcast_category = $category_model - ->where('code', $podcast->category) + ->where('id', $podcast->category_id) ->first(); $itunes_namespace = 'http://www.itunes.com/dtds/podcast-1.0.dtd'; @@ -50,7 +50,7 @@ function get_rss_feed($podcast) ); $channel->addChild( 'generator', - 'Castopod 0.0.0-development - https://castopod.org' + 'Castopod 0.0.0-development - https://castopod.org/' ); $channel->addChild('docs', 'https://cyber.harvard.edu/rss/rss.html'); diff --git a/app/Language/en/Breadcrumb.php b/app/Language/en/Breadcrumb.php index bd5d4b61d12dfaa760dd2f7848802ac53c325352..d434287bccc96e39be50c915e5593fe3b0832460 100644 --- a/app/Language/en/Breadcrumb.php +++ b/app/Language/en/Breadcrumb.php @@ -19,4 +19,5 @@ return [ 'users' => 'users', 'my-account' => 'my account', 'change-password' => 'change password', + 'import' => 'feed import', ]; diff --git a/app/Language/en/Podcast.php b/app/Language/en/Podcast.php index b64c6725464b131d4e7416e6cf3abeff77289498..8a6bda75a283f2789f6350edd13811985a07a351 100644 --- a/app/Language/en/Podcast.php +++ b/app/Language/en/Podcast.php @@ -10,6 +10,7 @@ return [ 'all_podcasts' => 'All podcasts', 'no_podcast' => 'No podcast found!', 'create' => 'Create a Podcast', + 'import' => 'Create and Import a Podcast from an existing Feed', 'new_episode' => 'New Episode', 'feed' => 'RSS feed', 'view' => 'View podcast', @@ -21,10 +22,10 @@ return [ 'form' => [ 'title' => 'Title', 'title_help' => - 'This podcast title. It will be shown on all podcasts platforms (such as Apple Podcasts) and players (such as Podcast Addict).', + 'The podcast title will be shown on all podcasts platforms (such as Apple Podcasts) and players (such as Podcast Addict).', 'name' => 'Name', 'name_help' => - 'This podcast name. It will be used in the URL address. It will be used as a Fedivers actor name, (for instance, it will be the podcast Mastodon’s name).', + 'The podcast will be used in the URL address. It will be used as a Fediverse actor name, (for instance, it will be the podcast Mastodon’s name).', 'description' => 'Description', 'description_help' => 'It will be shown on all podcasts platforms (such as Apple Podcasts) and players (such as Podcast Addict).', @@ -33,7 +34,7 @@ return [ 'This text will be automatically added at the end of each episode description, so that you don’t have to copy/paste it a gazillion times.', 'image' => 'Image', 'image_help' => - 'This podcast image. It should be square, JPEG or PNG, minimum 1400 x 1400 pixels and maximum 3000 x 3000 pixels.', + 'This podcast image. It must be square, JPEG or PNG, minimum 1400 x 1400 pixels and maximum 3000 x 3000 pixels.', 'language' => 'Language', 'language_help' => 'The language spoken on the podcast.', 'category' => 'Category', @@ -44,10 +45,10 @@ return [ 'The podcast parental advisory information. Does it contain explicit content?', '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.', + '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.', 'owner_email' => 'Owner email', 'owner_email_help' => - 'The podcast owner contact e-mail. For administrative use only. It will mostly be used by some platforms to verify this podcast ownerhip. 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.', + 'It will be used by most platforms to verify this podcast ownership. 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.', '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’.', @@ -75,6 +76,36 @@ return [ 'submit_create' => 'Create podcast', 'submit_edit' => 'Save podcast', ], + 'form_import' => [ + 'name' => 'Name', + 'name_help' => + 'This podcast name. It will be used in the URL address. It will be used as a Fediverse actor name, (for instance, it will be the podcast Mastodon’s name).', + 'imported_feed_url' => 'Feed URL', + 'imported_feed_url_help' => + 'Make sure you are legally allowed to copy that podcast.', + 'force_renumber' => 'Force episodes renumbering', + 'force_renumber_help' => + 'Use this if your old podcast does not have number but you want some on your new one.', + 'season_number' => 'Season number', + 'season_number_help' => + 'Use this if your old podcast does not have season number but you want one on your new one. Leave blank otherwise.', + 'slug_field' => [ + 'label' => 'Which field should be used to calculate episode slug', + 'link' => '<link>', + 'title' => '<title>', + ], + 'description_field' => [ + 'label' => 'Source field used for episode description / show notes', + 'description' => '<description>', + 'summary' => '<itunes:summary>', + 'subtitle_summary' => + '<itunes:subtitle> <itunes:summary>', + ], + 'max_episodes' => 'Maximum number of episodes to import', + 'max_episodes_helper' => 'Leave blank to import all episodes', + 'submit_import' => 'Import podcast', + 'submit_importing' => 'Importing podcast, this could take a while…', + ], 'category_options' => [ 'uncategorized' => 'uncategorized', 'arts' => 'Arts', diff --git a/app/Models/EpisodeModel.php b/app/Models/EpisodeModel.php index 9f911d770bb681d35dac9de95120bf6899d24ad5..b346fcff646016705b78d03b4db2d77083f6197c 100644 --- a/app/Models/EpisodeModel.php +++ b/app/Models/EpisodeModel.php @@ -17,6 +17,7 @@ class EpisodeModel extends Model protected $allowedFields = [ 'podcast_id', + 'guid', 'title', 'slug', 'enclosure_uri', @@ -44,8 +45,8 @@ class EpisodeModel extends Model 'enclosure_uri' => 'required', 'description' => 'required', 'image_uri' => 'required', - 'number' => 'required|is_natural_no_zero', - 'season_number' => 'required|is_natural_no_zero', + 'number' => 'is_natural_no_zero|permit_empty', + 'season_number' => 'is_natural_no_zero|permit_empty', 'type' => 'required', 'published_at' => 'valid_date|permit_empty', 'created_by' => 'required', diff --git a/app/Models/PodcastModel.php b/app/Models/PodcastModel.php index 9a3bc6ac4900f4dce293db2361f0fdeee74e9d6c..023f7a234103094de94dd20b4108ff116568b535 100644 --- a/app/Models/PodcastModel.php +++ b/app/Models/PodcastModel.php @@ -23,7 +23,7 @@ class PodcastModel extends Model 'episode_description_footer', 'image_uri', 'language', - 'category', + 'category_id', 'explicit', 'owner_name', 'owner_email', @@ -35,6 +35,7 @@ class PodcastModel extends Model 'custom_html_head', 'created_by', 'updated_by', + 'imported_feed_url', ]; protected $returnType = \App\Entities\Podcast::class; @@ -49,8 +50,7 @@ class PodcastModel extends Model 'description' => 'required', 'image_uri' => 'required', 'language' => 'required', - 'category' => 'required', - 'owner_name' => 'required', + 'category_id' => 'required', 'owner_email' => 'required|valid_email', 'type' => 'required', 'created_by' => 'required', diff --git a/app/Views/admin/podcast/edit.php b/app/Views/admin/podcast/edit.php index 7581a64831c57e6ead822c45111e7618400fb0ff..99c597e80fc86557d65663c1aad4c9eaf460fedf 100644 --- a/app/Views/admin/podcast/edit.php +++ b/app/Views/admin/podcast/edit.php @@ -92,7 +92,7 @@ <?= form_dropdown( 'category', $categoryOptions, - old('category', $podcast->category), + old('category', $podcast->category_id), [ 'id' => 'category', 'class' => 'form-select mb-4', diff --git a/app/Views/admin/podcast/import.php b/app/Views/admin/podcast/import.php new file mode 100644 index 0000000000000000000000000000000000000000..5bbaf110bd25bb30993034a95c10da4f01a7f584 --- /dev/null +++ b/app/Views/admin/podcast/import.php @@ -0,0 +1,163 @@ +<?= $this->extend('admin/_layout') ?> + +<?= $this->section('title') ?> +<?= lang('Podcast.import') ?> +<?= $this->endSection() ?> + + +<?= $this->section('content') ?> + +<?= form_open_multipart(route_to('podcast_import'), [ + 'method' => 'post', + 'class' => 'flex flex-col max-w-md', +]) ?> +<?= csrf_field() ?> + + +<div class="flex flex-col mb-4"> + <label for="name"><?= lang('Podcast.form_import.name') ?></label> + <input type="text" class="form-input" id="name" name="name" value="<?= old( + 'name' + ) ?>" required /> +</div> + +<div class="flex flex-col mb-4"> + <label for="name"><?= lang( + 'Podcast.form_import.imported_feed_url' + ) ?></label> + <input type="text" class="form-input" id="imported_feed_url" name="imported_feed_url" value="<?= old( + 'imported_feed_url' + ) ?>" required /> +</div> + +<?= form_label(lang('Podcast.form.language'), 'language') ?> +<?= form_dropdown('language', $languageOptions, old('language', $browserLang), [ + 'id' => 'language', + 'class' => 'form-select mb-4', + 'required' => 'required', +]) ?> + +<?= form_label(lang('Podcast.form.category'), 'category') ?> +<?= form_dropdown('category', $categoryOptions, old('category'), [ + 'id' => 'category', + 'class' => 'form-select mb-4', + 'required' => 'required', +]) ?> + +<?= form_fieldset(lang('Podcast.form_import.slug_field.label'), [ + 'class' => 'flex flex-col mb-4', +]) ?> + <label for="link" class="inline-flex items-center"> + <?= form_radio( + ['id' => 'link', 'name' => 'slug_field', 'class' => 'form-radio'], + 'link', + old('slug_field') ? old('slug_field') == 'link' : true + ) ?> + <span class="ml-2"><?= lang( + 'Podcast.form_import.slug_field.link' + ) ?></span> + </label> + <label for="title" class="inline-flex items-center"> + <?= form_radio( + ['id' => 'title', 'name' => 'slug_field', 'class' => 'form-radio'], + 'title', + old('slug_field') ? old('slug_field') == 'title' : false + ) ?> + <span class="ml-2"><?= lang( + 'Podcast.form_import.slug_field.title' + ) ?></span> + </label> +<?= form_fieldset_close() ?> + +<?= form_fieldset(lang('Podcast.form_import.description_field.label'), [ + 'class' => 'flex flex-col mb-4', +]) ?> + <label for="description" class="inline-flex items-center"> + <?= form_radio( + [ + 'id' => 'description', + 'name' => 'description_field', + 'class' => 'form-radio', + ], + 'description', + old('description_field') + ? old('description_field') == 'description' + : true + ) ?> + <span class="ml-2"><?= lang( + 'Podcast.form_import.description_field.description' + ) ?></span> + </label> + <label for="subtitle_summary" class="inline-flex items-center"> + <?= form_radio( + [ + 'id' => 'summary', + 'name' => 'description_field', + 'class' => 'form-radio', + ], + 'summary', + old('description_field') + ? old('description_field') == 'summary' + : false + ) ?> + <span class="ml-2"><?= lang( + 'Podcast.form_import.description_field.summary' + ) ?></span> + </label> + <label for="subtitle_summary" class="inline-flex items-center"> + <?= form_radio( + [ + 'id' => 'subtitle_summary', + 'name' => 'description_field', + 'class' => 'form-radio', + ], + 'subtitle_summary', + old('description_field') + ? old('description_field') == 'subtitle_summary' + : false + ) ?> + <span class="ml-2"><?= lang( + 'Podcast.form_import.description_field.subtitle_summary' + ) ?></span> + </label> +<?= form_fieldset_close() ?> + + +<label class="inline-flex items-center mb-4"> + <?= form_checkbox( + [ + 'id' => 'force_renumber', + 'name' => 'force_renumber', + 'class' => 'form-checkbox', + ], + 'yes', + old('force_renumber', false) + ) ?> + <span class="ml-2"><?= lang('Podcast.form_import.force_renumber') ?></span> +</label> + +<div class="flex flex-col mb-4"> + <label for="name"><?= lang('Podcast.form_import.season_number') ?></label> + <input type="text" class="form-input" id="season_number" name="season_number" value="<?= old( + 'season_number' + ) ?>" /> +</div> + +<div class="flex flex-col mb-4"> + <label for="max_episodes"><?= lang( + 'Podcast.form_import.max_episodes' + ) ?></label> + <input type="text" class="form-input" id="max_episodes" name="max_episodes" value="<?= old( + 'max_episodes' + ) ?>" /> +</div> + +<button type="submit" name="submit" onsubmit="this.disabled=true; this.value='<?= lang( + 'Podcast.form_import.submit_importing' +) ?>';" class="self-end px-4 py-2 bg-gray-200"><?= lang( + 'Podcast.form_import.submit_import' +) ?></button> +<?= form_close() ?> + + +<?= $this->endSection() ?> diff --git a/app/Views/admin/podcast/list.php b/app/Views/admin/podcast/list.php index 83768a7cbefe70646fce062f16604c892ea6e964..cc1097cf326a2a98fc5dde1f94a54e19af49e3c6 100644 --- a/app/Views/admin/podcast/list.php +++ b/app/Views/admin/podcast/list.php @@ -7,6 +7,11 @@ ) ?>"> <?= icon('add', 'mr-2') ?> <?= lang('Podcast.create') ?></a> +<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-import' +) ?>"> +<?= icon('add', 'mr-2') ?> +<?= lang('Podcast.import') ?></a> <?= $this->endSection() ?> diff --git a/composer.json b/composer.json index 44be1c8c6563b40442ea34f620644b0b441131ef..dc3cf041ac06fd52060e02dc7347e11af1a8ebf6 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,8 @@ "myth/auth": "dev-develop", "codeigniter4/codeigniter4": "dev-develop", "league/commonmark": "^1.5", - "vlucas/phpdotenv": "^5.1" + "vlucas/phpdotenv": "^5.1", + "league/html-to-markdown": "^4.10" }, "require-dev": { "mikey179/vfsstream": "1.6.*", diff --git a/composer.lock b/composer.lock index bdeba108ecebe0921036973e38a145bfae962759..e487409e94204a4dbbc1f806c0cdf523eb828d49 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a6be291e1c7f73b73182cd7b49234688", + "content-hash": "38eeae7f5d0143863430cda9df10d487", "packages": [ { "name": "codeigniter4/codeigniter4", @@ -12,12 +12,12 @@ "source": { "type": "git", "url": "https://github.com/codeigniter4/CodeIgniter4.git", - "reference": "cbfc8d27645fc9fe19d540c796b627852b4a1142" + "reference": "9a7e826138bf8940ef8c7a25d59d67b1aebfe0ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/codeigniter4/CodeIgniter4/zipball/cbfc8d27645fc9fe19d540c796b627852b4a1142", - "reference": "cbfc8d27645fc9fe19d540c796b627852b4a1142", + "url": "https://api.github.com/repos/codeigniter4/CodeIgniter4/zipball/9a7e826138bf8940ef8c7a25d59d67b1aebfe0ee", + "reference": "9a7e826138bf8940ef8c7a25d59d67b1aebfe0ee", "shasum": "" }, "require": { @@ -34,6 +34,7 @@ "codeigniter4/codeigniter4-standard": "^1.0", "fzaninotto/faker": "^1.9@dev", "mikey179/vfsstream": "1.6.*", + "phpstan/phpstan": "^0.12.37", "phpunit/phpunit": "^8.5", "predis/predis": "^1.1", "squizlabs/php_codesniffer": "^3.3" @@ -65,7 +66,7 @@ "slack": "https://codeigniterchat.slack.com", "issues": "https://github.com/codeigniter4/CodeIgniter4/issues" }, - "time": "2020-08-04T03:43:32+00:00" + "time": "2020-08-17T14:11:23+00:00" }, { "name": "composer/ca-bundle", @@ -601,31 +602,113 @@ ], "time": "2020-07-19T22:47:30+00:00" }, + { + "name": "league/html-to-markdown", + "version": "4.10.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/html-to-markdown.git", + "reference": "0868ae7a552e809e5cd8f93ba022071640408e88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/html-to-markdown/zipball/0868ae7a552e809e5cd8f93ba022071640408e88", + "reference": "0868ae7a552e809e5cd8f93ba022071640408e88", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xml": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "mikehaertl/php-shellcommand": "~1.1.0", + "phpunit/phpunit": "^4.8|^5.7", + "scrutinizer/ocular": "~1.1" + }, + "bin": [ + "bin/html-to-markdown" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.10-dev" + } + }, + "autoload": { + "psr-4": { + "League\\HTMLToMarkdown\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + }, + { + "name": "Nick Cernis", + "email": "nick@cern.is", + "homepage": "http://modernnerd.net", + "role": "Original Author" + } + ], + "description": "An HTML-to-markdown conversion helper for PHP", + "homepage": "https://github.com/thephpleague/html-to-markdown", + "keywords": [ + "html", + "markdown" + ], + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + }, + { + "url": "https://www.patreon.com/colinodell", + "type": "patreon" + } + ], + "time": "2020-07-01T00:34:03+00:00" + }, { "name": "maxmind-db/reader", - "version": "v1.6.0", + "version": "v1.7.0", "source": { "type": "git", "url": "https://github.com/maxmind/MaxMind-DB-Reader-php.git", - "reference": "febd4920bf17c1da84cef58e56a8227dfb37fbe4" + "reference": "942553da239f12051275f9c666538b5dd09e2908" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maxmind/MaxMind-DB-Reader-php/zipball/febd4920bf17c1da84cef58e56a8227dfb37fbe4", - "reference": "febd4920bf17c1da84cef58e56a8227dfb37fbe4", + "url": "https://api.github.com/repos/maxmind/MaxMind-DB-Reader-php/zipball/942553da239f12051275f9c666538b5dd09e2908", + "reference": "942553da239f12051275f9c666538b5dd09e2908", "shasum": "" }, "require": { - "php": ">=5.6" + "php": ">=7.2" }, "conflict": { - "ext-maxminddb": "<1.6.0,>=2.0.0" + "ext-maxminddb": "<1.7.0,>=2.0.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "2.*", "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpcov": "^3.0", - "phpunit/phpunit": "5.*", + "phpunit/phpcov": ">=6.0.0", + "phpunit/phpunit": ">=8.0.0,<10.0.0", "squizlabs/php_codesniffer": "3.*" }, "suggest": { @@ -659,7 +742,7 @@ "geolocation", "maxmind" ], - "time": "2019-12-19T22:59:03+00:00" + "time": "2020-08-07T22:10:05+00:00" }, { "name": "maxmind/web-service-common", @@ -1618,16 +1701,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.2.0", + "version": "5.2.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "3170448f5769fe19f456173d833734e0ff1b84df" + "reference": "d870572532cd70bc3fab58f2e23ad423c8404c44" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/3170448f5769fe19f456173d833734e0ff1b84df", - "reference": "3170448f5769fe19f456173d833734e0ff1b84df", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d870572532cd70bc3fab58f2e23ad423c8404c44", + "reference": "d870572532cd70bc3fab58f2e23ad423c8404c44", "shasum": "" }, "require": { @@ -1666,7 +1749,7 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2020-07-20T20:05:34+00:00" + "time": "2020-08-15T11:14:08+00:00" }, { "name": "phpdocumentor/type-resolver", @@ -2026,6 +2109,7 @@ "keywords": [ "tokenizer" ], + "abandoned": true, "time": "2019-09-17T06:23:10+00:00" }, { @@ -2738,16 +2822,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.5.5", + "version": "3.5.6", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "73e2e7f57d958e7228fce50dc0c61f58f017f9f6" + "reference": "e97627871a7eab2f70e59166072a6b767d5834e0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/73e2e7f57d958e7228fce50dc0c61f58f017f9f6", - "reference": "73e2e7f57d958e7228fce50dc0c61f58f017f9f6", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/e97627871a7eab2f70e59166072a6b767d5834e0", + "reference": "e97627871a7eab2f70e59166072a6b767d5834e0", "shasum": "" }, "require": { @@ -2785,7 +2869,7 @@ "phpcs", "standards" ], - "time": "2020-04-17T01:09:41+00:00" + "time": "2020-08-10T04:50:15+00:00" }, { "name": "theseer/tokenizer", diff --git a/docs/setup-development.md b/docs/setup-development.md index 47668c9aef34075c353d71b8fc2bce6c0bc824b9..66e02a3c561ca3c74cb52b183c3def8afbd67289 100644 --- a/docs/setup-development.md +++ b/docs/setup-development.md @@ -106,12 +106,24 @@ docker-compose run --rm app php spark migrate -all 2. Populate the database with the required data: +```bash +# Populates all required data +docker-compose run --rm app php spark db:seed AppSeeder +``` + +You may also add only data you chose: + ```bash # Populates all categories docker-compose run --rm app php spark db:seed CategorySeeder +# Populates all Languages docker-compose run --rm app php spark db:seed LanguageSeeder +# Populates all podcasts platforms docker-compose run --rm app php spark db:seed PlatformSeeder +# Populates all Authentication data (roles definition…) docker-compose run --rm app php spark db:seed AuthSeeder +# Populates test data (login: admin / password: AGUehL3P) +docker-compose run --rm app php spark db:seed TestSeeder ``` 3. (optionnal) Populate the database with test data: