mirror of
https://code.castopod.org/adaures/castopod
synced 2025-04-19 13:01:19 +00:00
feat: import podcast from an rss feed url
* add podcast import form * add League\\HTMLToMarkdown * add guid field in podcast table * change podcast category from string to id closes #21
This commit is contained in:
parent
9c224a8ac6
commit
9a5d5a15b4
@ -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))
|
||||
|
@ -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
|
||||
|
@ -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');
|
||||
|
@ -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');
|
||||
|
@ -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');
|
||||
|
@ -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',
|
||||
|
@ -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',
|
||||
|
@ -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/',
|
||||
|
@ -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()
|
||||
|
@ -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');
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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');
|
||||
|
||||
|
@ -19,4 +19,5 @@ return [
|
||||
'users' => 'users',
|
||||
'my-account' => 'my account',
|
||||
'change-password' => 'change password',
|
||||
'import' => 'feed import',
|
||||
];
|
||||
|
@ -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',
|
||||
|
@ -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',
|
||||
|
@ -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',
|
||||
|
@ -92,7 +92,7 @@
|
||||
<?= form_dropdown(
|
||||
'category',
|
||||
$categoryOptions,
|
||||
old('category', $podcast->category),
|
||||
old('category', $podcast->category_id),
|
||||
[
|
||||
'id' => 'category',
|
||||
'class' => 'form-select mb-4',
|
||||
|
163
app/Views/admin/podcast/import.php
Normal file
163
app/Views/admin/podcast/import.php
Normal file
@ -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() ?>
|
@ -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() ?>
|
||||
|
||||
|
||||
|
@ -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.*",
|
||||
|
136
composer.lock
generated
136
composer.lock
generated
@ -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",
|
||||
@ -602,30 +603,112 @@
|
||||
"time": "2020-07-19T22:47:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "maxmind-db/reader",
|
||||
"version": "v1.6.0",
|
||||
"name": "league/html-to-markdown",
|
||||
"version": "4.10.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/maxmind/MaxMind-DB-Reader-php.git",
|
||||
"reference": "febd4920bf17c1da84cef58e56a8227dfb37fbe4"
|
||||
"url": "https://github.com/thephpleague/html-to-markdown.git",
|
||||
"reference": "0868ae7a552e809e5cd8f93ba022071640408e88"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/maxmind/MaxMind-DB-Reader-php/zipball/febd4920bf17c1da84cef58e56a8227dfb37fbe4",
|
||||
"reference": "febd4920bf17c1da84cef58e56a8227dfb37fbe4",
|
||||
"url": "https://api.github.com/repos/thephpleague/html-to-markdown/zipball/0868ae7a552e809e5cd8f93ba022071640408e88",
|
||||
"reference": "0868ae7a552e809e5cd8f93ba022071640408e88",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.6"
|
||||
"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.7.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/maxmind/MaxMind-DB-Reader-php.git",
|
||||
"reference": "942553da239f12051275f9c666538b5dd09e2908"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/maxmind/MaxMind-DB-Reader-php/zipball/942553da239f12051275f9c666538b5dd09e2908",
|
||||
"reference": "942553da239f12051275f9c666538b5dd09e2908",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"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",
|
||||
|
@ -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:
|
||||
|
Loading…
x
Reference in New Issue
Block a user