Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • alpha
  • beta
  • develop
  • docs/fix-readme
  • docs/update-vitepress
  • draft/rss-feed
  • feat/dashboard
  • feat/episodes-page-ux
  • feat/generator-user-agent
  • feat/headliner
  • feat/new-languages
  • feat/plugins
  • fix/federation
  • fix/forms-ux
  • i18n
  • main
  • next
  • refactor/transcripts
  • v1.0.0
  • v1.0.0-alpha.1
  • v1.0.0-alpha.10
  • v1.0.0-alpha.11
  • v1.0.0-alpha.12
  • v1.0.0-alpha.13
  • v1.0.0-alpha.14
  • v1.0.0-alpha.15
  • v1.0.0-alpha.16
  • v1.0.0-alpha.17
  • v1.0.0-alpha.18
  • v1.0.0-alpha.19
  • v1.0.0-alpha.2
  • v1.0.0-alpha.20
  • v1.0.0-alpha.21
  • v1.0.0-alpha.22
  • v1.0.0-alpha.23
  • v1.0.0-alpha.24
  • v1.0.0-alpha.25
  • v1.0.0-alpha.26
  • v1.0.0-alpha.27
  • v1.0.0-alpha.28
  • v1.0.0-alpha.29
  • v1.0.0-alpha.3
  • v1.0.0-alpha.30
  • v1.0.0-alpha.31
  • v1.0.0-alpha.32
  • v1.0.0-alpha.33
  • v1.0.0-alpha.34
  • v1.0.0-alpha.35
  • v1.0.0-alpha.36
  • v1.0.0-alpha.37
  • v1.0.0-alpha.38
  • v1.0.0-alpha.39
  • v1.0.0-alpha.4
  • v1.0.0-alpha.40
  • v1.0.0-alpha.41
  • v1.0.0-alpha.42
  • v1.0.0-alpha.43
  • v1.0.0-alpha.44
  • v1.0.0-alpha.45
  • v1.0.0-alpha.46
  • v1.0.0-alpha.47
  • v1.0.0-alpha.48
  • v1.0.0-alpha.49
  • v1.0.0-alpha.5
  • v1.0.0-alpha.50
  • v1.0.0-alpha.51
  • v1.0.0-alpha.52
  • v1.0.0-alpha.53
  • v1.0.0-alpha.54
  • v1.0.0-alpha.55
  • v1.0.0-alpha.56
  • v1.0.0-alpha.57
  • v1.0.0-alpha.58
  • v1.0.0-alpha.59
  • v1.0.0-alpha.6
  • v1.0.0-alpha.60
  • v1.0.0-alpha.61
  • v1.0.0-alpha.62
  • v1.0.0-alpha.63
  • v1.0.0-alpha.64
  • v1.0.0-alpha.65
  • v1.0.0-alpha.66
  • v1.0.0-alpha.67
  • v1.0.0-alpha.68
  • v1.0.0-alpha.69
  • v1.0.0-alpha.7
  • v1.0.0-alpha.70
  • v1.0.0-alpha.71
  • v1.0.0-alpha.72
  • v1.0.0-alpha.73
  • v1.0.0-alpha.74
  • v1.0.0-alpha.75
  • v1.0.0-alpha.76
  • v1.0.0-alpha.77
  • v1.0.0-alpha.78
  • v1.0.0-alpha.79
  • v1.0.0-alpha.8
  • v1.0.0-alpha.80
  • v1.0.0-alpha.9
  • v1.0.0-beta.1
  • v1.0.0-beta.10
  • v1.0.0-beta.11
  • v1.0.0-beta.12
  • v1.0.0-beta.13
  • v1.0.0-beta.14
  • v1.0.0-beta.15
  • v1.0.0-beta.16
  • v1.0.0-beta.17
  • v1.0.0-beta.18
  • v1.0.0-beta.19
  • v1.0.0-beta.2
  • v1.0.0-beta.20
  • v1.0.0-beta.21
  • v1.0.0-beta.22
  • v1.0.0-beta.23
  • v1.0.0-beta.24
  • v1.0.0-beta.3
  • v1.0.0-beta.4
118 results

Target

Select target project
  • adaures/castopod
  • mkljczk/castopod-host
  • spaetz/castopod-host
  • PatrykMis/castopod
  • jonas/castopod
  • ajeremias/castopod
  • misuzu/castopod
  • KrzysztofDomanczyk/castopod
  • Behel/castopod
  • nebulon/castopod
  • ewen/castopod
  • NeoluxConsulting/castopod
  • nateritter/castopod-og
  • prcutler/castopod
14 results
Select Git revision
  • alpha
  • beta
  • develop
  • docs/fix-readme
  • docs/update-vitepress
  • draft/rss-feed
  • feat/dashboard
  • feat/episodes-page-ux
  • feat/generator-user-agent
  • feat/headliner
  • feat/new-languages
  • feat/plugins
  • fix/federation
  • fix/forms-ux
  • i18n
  • main
  • next
  • refactor/transcripts
  • v1.0.0
  • v1.0.0-alpha.1
  • v1.0.0-alpha.10
  • v1.0.0-alpha.11
  • v1.0.0-alpha.12
  • v1.0.0-alpha.13
  • v1.0.0-alpha.14
  • v1.0.0-alpha.15
  • v1.0.0-alpha.16
  • v1.0.0-alpha.17
  • v1.0.0-alpha.18
  • v1.0.0-alpha.19
  • v1.0.0-alpha.2
  • v1.0.0-alpha.20
  • v1.0.0-alpha.21
  • v1.0.0-alpha.22
  • v1.0.0-alpha.23
  • v1.0.0-alpha.24
  • v1.0.0-alpha.25
  • v1.0.0-alpha.26
  • v1.0.0-alpha.27
  • v1.0.0-alpha.28
  • v1.0.0-alpha.29
  • v1.0.0-alpha.3
  • v1.0.0-alpha.30
  • v1.0.0-alpha.31
  • v1.0.0-alpha.32
  • v1.0.0-alpha.33
  • v1.0.0-alpha.34
  • v1.0.0-alpha.35
  • v1.0.0-alpha.36
  • v1.0.0-alpha.37
  • v1.0.0-alpha.38
  • v1.0.0-alpha.39
  • v1.0.0-alpha.4
  • v1.0.0-alpha.40
  • v1.0.0-alpha.41
  • v1.0.0-alpha.42
  • v1.0.0-alpha.43
  • v1.0.0-alpha.44
  • v1.0.0-alpha.45
  • v1.0.0-alpha.46
  • v1.0.0-alpha.47
  • v1.0.0-alpha.48
  • v1.0.0-alpha.49
  • v1.0.0-alpha.5
  • v1.0.0-alpha.50
  • v1.0.0-alpha.51
  • v1.0.0-alpha.52
  • v1.0.0-alpha.53
  • v1.0.0-alpha.54
  • v1.0.0-alpha.55
  • v1.0.0-alpha.56
  • v1.0.0-alpha.57
  • v1.0.0-alpha.58
  • v1.0.0-alpha.59
  • v1.0.0-alpha.6
  • v1.0.0-alpha.60
  • v1.0.0-alpha.61
  • v1.0.0-alpha.62
  • v1.0.0-alpha.63
  • v1.0.0-alpha.64
  • v1.0.0-alpha.65
  • v1.0.0-alpha.66
  • v1.0.0-alpha.67
  • v1.0.0-alpha.68
  • v1.0.0-alpha.69
  • v1.0.0-alpha.7
  • v1.0.0-alpha.70
  • v1.0.0-alpha.71
  • v1.0.0-alpha.72
  • v1.0.0-alpha.73
  • v1.0.0-alpha.74
  • v1.0.0-alpha.75
  • v1.0.0-alpha.76
  • v1.0.0-alpha.77
  • v1.0.0-alpha.78
  • v1.0.0-alpha.79
  • v1.0.0-alpha.8
  • v1.0.0-alpha.80
  • v1.0.0-alpha.9
  • v1.0.0-beta.1
  • v1.0.0-beta.10
  • v1.0.0-beta.11
  • v1.0.0-beta.12
  • v1.0.0-beta.13
  • v1.0.0-beta.14
  • v1.0.0-beta.15
  • v1.0.0-beta.16
  • v1.0.0-beta.17
  • v1.0.0-beta.18
  • v1.0.0-beta.19
  • v1.0.0-beta.2
  • v1.0.0-beta.20
  • v1.0.0-beta.21
  • v1.0.0-beta.22
  • v1.0.0-beta.23
  • v1.0.0-beta.24
  • v1.0.0-beta.3
  • v1.0.0-beta.4
118 results
Show changes
Showing
with 964 additions and 29 deletions
<?php
/**
* @copyright 2021 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Models;
use CodeIgniter\Model;
class PersonModel extends Model
{
protected $table = 'persons';
protected $primaryKey = 'id';
protected $allowedFields = [
'id',
'full_name',
'unique_name',
'information_url',
'image_uri',
'created_by',
'updated_by',
];
protected $returnType = \App\Entities\Person::class;
protected $useSoftDeletes = false;
protected $useTimestamps = true;
protected $validationRules = [
'full_name' => 'required',
'unique_name' =>
'required|regex_match[/^[a-z0-9\-]{1,191}$/]|is_unique[persons.unique_name,id,{id}]',
'image_uri' => 'required',
'created_by' => 'required',
'updated_by' => 'required',
];
protected $validationMessages = [];
// clear cache before update if by any chance, the person name changes, so will the person link
protected $afterInsert = ['clearCache'];
protected $beforeUpdate = ['clearCache'];
protected $beforeDelete = ['clearCache'];
public function getPersonById($personId)
{
if (!($found = cache("person{$personId}"))) {
$found = $this->find($personId);
cache()->save("person{$personId}", $found, DECADE);
}
return $found;
}
public function getPerson($fullName)
{
return $this->where('full_name', $fullName)->first();
}
public function createPerson($fullName, $informationUrl, $image)
{
$person = new \App\Entities\Person([
'full_name' => $fullName,
'unique_name' => slugify($fullName),
'information_url' => $informationUrl,
'image' => download_file($image),
'created_by' => user()->id,
'updated_by' => user()->id,
]);
return $this->insert($person);
}
public function getPersonOptions()
{
$options = [];
if (!($options = cache('person_options'))) {
$options = array_reduce(
$this->select('`id`, `full_name`')
->orderBy('`full_name`', 'ASC')
->findAll(),
function ($result, $person) {
$result[$person->id] = $person->full_name;
return $result;
},
[]
);
cache()->save('person_options', $options, DECADE);
}
return $options;
}
public function getTaxonomyOptions()
{
$options = [];
$locale = service('request')->getLocale();
if (!($options = cache("taxonomy_options_{$locale}"))) {
foreach (lang('PersonsTaxonomy.persons') as $group_key => $group) {
foreach ($group['roles'] as $role_key => $role) {
$options[
"$group_key,$role_key"
] = "{$group['label']}{$role['label']}";
}
}
cache()->save("taxonomy_options_{$locale}", $options, DECADE);
}
return $options;
}
protected function clearCache(array $data)
{
$person = (new PersonModel())->getPersonById(
is_array($data['id']) ? $data['id'][0] : $data['id']
);
cache()->delete('person_options');
cache()->delete("person{$person->id}");
cache()->delete("user{$person->created_by}_persons");
$supportedLocales = config('App')->supportedLocales;
// clear cache for every credit page
foreach ($supportedLocales as $locale) {
cache()->delete("credit_{$locale}");
}
return $data;
}
}
...@@ -16,14 +16,20 @@ use CodeIgniter\Model; ...@@ -16,14 +16,20 @@ use CodeIgniter\Model;
class PlatformModel extends Model class PlatformModel extends Model
{ {
protected $table = 'platforms'; protected $table = 'platforms';
protected $primaryKey = 'id'; protected $primaryKey = 'slug';
protected $allowedFields = ['slug', 'label', 'home_url', 'submit_url']; protected $allowedFields = [
'slug',
'type',
'label',
'home_url',
'submit_url',
];
protected $returnType = \App\Entities\Platform::class; protected $returnType = \App\Entities\Platform::class;
protected $useSoftDeletes = false; protected $useSoftDeletes = false;
protected $useTimestamps = true; protected $useTimestamps = false;
public function getPlatforms() public function getPlatforms()
{ {
...@@ -37,26 +43,32 @@ class PlatformModel extends Model ...@@ -37,26 +43,32 @@ class PlatformModel extends Model
return $found; return $found;
} }
public function getOrCreatePlatform($slug, $platformType) public function getPlatform($slug)
{ {
if (!($found = cache("platforms_$slug"))) { if (!($found = cache("platform_$slug"))) {
$found = $this->where('slug', $slug)->first(); $found = $this->where('slug', $slug)->first();
if (!$found) { cache()->save("platform_$slug", $found, DECADE);
$data = [
'slug' => $slug,
'type' => $platformType,
'label' => $slug,
'home_url' => '',
'submit_url' => null,
];
$this->insert($data);
$found = $this->where('slug', $slug)->first();
}
cache()->save("platforms_$slug", $found, DECADE);
} }
return $found; return $found;
} }
public function createPlatform(
$slug,
$type,
$label,
$homeUrl,
$submitUrl = null
) {
$data = [
'slug' => $slug,
'type' => $type,
'label' => $label,
'home_url' => $homeUrl,
'submit_url' => $submitUrl,
];
return $this->insert($data, false);
}
public function getPlatformsWithLinks($podcastId, $platformType) public function getPlatformsWithLinks($podcastId, $platformType)
{ {
if ( if (
......
<?php <?php
/** /**
* @copyright 2020 Podlibre * @copyright 2021 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/ * @link https://castopod.org/
*/ */
...@@ -170,7 +170,7 @@ class PodcastModel extends Model ...@@ -170,7 +170,7 @@ class PodcastModel extends Model
: false; : false;
} }
protected function clearCache(array $data) public function clearCache(array $data)
{ {
$podcast = (new PodcastModel())->getPodcastById( $podcast = (new PodcastModel())->getPodcastById(
is_array($data['id']) ? $data['id'][0] : $data['id'] is_array($data['id']) ? $data['id'][0] : $data['id']
...@@ -195,6 +195,10 @@ class PodcastModel extends Model ...@@ -195,6 +195,10 @@ class PodcastModel extends Model
); );
} }
} }
// clear cache for every credit page
foreach ($supportedLocales as $locale) {
cache()->delete("credits_{$locale}");
}
// delete episode lists cache per year / season // delete episode lists cache per year / season
// and localized pages // and localized pages
......
<?php
/**
* @copyright 2021 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Models;
use CodeIgniter\Model;
class PodcastPersonModel extends Model
{
protected $table = 'podcasts_persons';
protected $primaryKey = 'id';
protected $allowedFields = [
'id',
'podcast_id',
'person_id',
'person_group',
'person_role',
];
protected $returnType = \App\Entities\PodcastPerson::class;
protected $useSoftDeletes = false;
protected $useTimestamps = false;
protected $validationRules = [
'podcast_id' => 'required',
'person_id' => 'required',
];
protected $validationMessages = [];
protected $afterInsert = ['clearCache'];
protected $beforeDelete = ['clearCache'];
public function getPersonsByPodcastId($podcastId)
{
if (!($found = cache("podcast{$podcastId}_persons"))) {
$found = $this->select('podcasts_persons.*')
->where('podcast_id', $podcastId)
->join(
'persons',
'person_id=persons.id'
)
->orderby('full_name')
->findAll();
cache()->save("podcast{$podcastId}_persons", $found, DECADE);
}
return $found;
}
/**
* Add persons to podcast
*
* @param int $podcastId
* @param array $persons
* @param array $groups_roles
*
* @return integer Number of rows inserted or FALSE on failure
*/
public function addPodcastPersons($podcastId, $persons, $groups_roles)
{
if (!empty($persons)) {
$this->clearCache(['id' => ['podcast_id' => $podcastId]]);
$data = [];
foreach ($persons as $person) {
if ($groups_roles) {
foreach ($groups_roles as $group_role) {
$group_role = explode(',', $group_role);
$data[] = [
'podcast_id' => $podcastId,
'person_id' => $person,
'person_group' => $group_role[0],
'person_role' => $group_role[1],
];
}
} else {
$data[] = [
'podcast_id' => $podcastId,
'person_id' => $person,
];
}
}
return $this->insertBatch($data);
}
return 0;
}
public function removePodcastPersons($podcastId, $podcastPersonId)
{
return $this->delete([
'podcast_id' => $podcastId,
'id' => $podcastPersonId,
]);
}
protected function clearCache(array $data)
{
$podcastId = null;
if (isset($data['id']['podcast_id'])) {
$podcastId = $data['id']['podcast_id'];
} else {
$person = (new PodcastPersonModel())->find(
is_array($data['id']) ? $data['id']['id'] : $data['id']
);
$podcastId = $person->podcast_id;
}
cache()->delete("podcast{$podcastId}_persons");
(new PodcastModel())->clearCache(['id' => $podcastId]);
return $data;
}
}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12.414 5H21a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h7.414l2 2zM4 5v14h16V7h-8.414l-2-2H4zm4 13a4 4 0 1 1 8 0H8zm4-5a2.5 2.5 0 1 1 0-5 2.5 2.5 0 0 1 0 5z"/></svg>
\ No newline at end of file
<svg version="1.1" viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg">
<rect width="300" height="300" rx="67" fill="#f2f8ff"/>
<g transform="matrix(1.36 0 0 1.36 -45.282 22.882)">
<path d="m133.88 120.47c4.08 12.49 10.64 23.85 19.12 33.5 15.88-2.06 29.85-10.22 39.46-22.06-7.84 2.07-16.07 3.17-24.56 3.17-6.79 0-13.42-.72-19.81-2.06-6.67-1.36-12.07-6.21-14.21-12.55z" fill="#1269ff"/>
<path d="m145.09 154.47c2.68 0 5.32-.17 7.91-.51-8.48-9.65-15.04-21.01-19.12-33.5-.64-1.91-.99-3.95-.99-6.07 0-4.77 1.76-9.12 4.66-12.46-11.1 12.41-19.02 27.71-22.48 44.64 8.85 5.03 19.1 7.9 30.02 7.9z" fill="#5c9dff"/>
<path d="m85.78 107.8c4 16.61 14.79 30.57 29.28 38.78 3.47-16.95 11.4-32.28 22.52-44.69 3.48-3.98 8.6-6.49 14.3-6.49 1.27 0 2.5.12 3.7.36-6.36-1.33-12.95-2.04-19.71-2.04-18.35-.01-35.5 5.14-50.09 14.08z" fill="#9ec6ff"/>
<path d="m155.59 95.754c-6.36-1.33-12.95-2.04-19.71-2.04-18.36 0-35.51 5.15-50.1 14.09-1.1-4.59-1.69-9.39-1.69-14.33 0-19.02 8.71-36.01 22.35-47.2 29.91 9.03 53.64 32.31 63.39 61.91.09.26.18.52.26.79-.08-.26-.17-.53-.26-.79-2.19-6.29-7.6-11.12-14.24-12.43z" fill="#d1e3ff"/>
<path d="m203.08 82.474c0-27-22.49-50-58-50-14.66 0-28.12 5.18-38.64 13.8 30.18 9.12 54.07 32.7 63.64 62.69.56 1.75 1.07 3.62 1.53 5.42 17.34-.38 31.47-14.48 31.47-31.91z" fill="#fff"/>
<path d="m151.89 98.394c1.12 0 2.06.09 3.12.3 5.56 1.1 10.12 5.14 11.98 10.43.54 1.7 1.25 4.25 1.7 6l.59 2.31 2.38-.05c18.94-.4 34.43-15.81 34.43-34.91 0-28.93-24.13-53-61-53-15.21.05-29.63 5.58-40.55 14.48-14.31 11.73-23.45 29.56-23.45 49.52.01 5.12.63 10.27 1.78 15.03 4.2 17.43 15.52 32.08 30.71 40.68 9.19 5.18 20.18 8.25 31.51 8.28 2.78 0 5.61-.19 8.29-.53 16.67-2.16 31.32-10.73 41.41-23.14l.66-.81-1.28-4.63-2.47.65c-7.66 2.03-15.51 3.08-23.8 3.07-6.63 0-12.95-.68-19.19-2-5.62-1.13-10.19-5.22-11.99-10.56-.58-1.68-.84-3.24-.83-5.11-.06-4.17 1.37-7.6 3.95-10.53 2.94-3.36 7.25-5.48 12.05-5.48zm-6.8-62.92c34.12 0 55 21.92 55 47 0 14.99-11.56 27.3-26.23 28.78-.39-1.39-.81-2.86-1.17-3.98-9.29-29.1-31.41-51.63-59.87-62.03 9.25-6.28 20.16-9.81 32.27-9.77zm-58 58c0-17.51 7.76-33.21 20.03-43.85 23.34 7.48 42.47 23.45 53.64 44.64-1.45-.64-2.98-1.13-4.58-1.45-6.5-1.35-13.39-2.1-20.3-2.1-17.28.03-33.84 4.58-48 12.4-.53-3.13-.79-6.31-.79-9.64zm60.39 42.48c6.53 1.37 13.47 2.12 20.42 2.12 5.31 0 10.59-.44 15.72-1.25-8.1 7.21-18.23 12.17-29.43 13.94-4.26-5-7.96-10.4-11.05-16.17 1.38.59 2.83 1.05 4.34 1.36zm-.45 15.49c-.64.02-1.29.03-1.94.03-9.73.03-18.59-2.26-26.63-6.45 2.31-10.17 6.25-19.6 11.58-28.14.18 1.57.53 3.13.99 4.52 3.57 10.89 9.08 21.14 16 30.04zm-11.69-51.54c-10.42 11.68-18.29 26.2-22.27 41.94-11.43-7.58-20.01-19.1-23.82-32.62 13.79-8.04 29.5-12.54 46.63-12.51.96 0 1.91.01 2.86.04-1.24.93-2.38 1.98-3.4 3.15z" fill="#003dad"/>
</g>
</svg>
...@@ -11,8 +11,8 @@ ...@@ -11,8 +11,8 @@
<link rel="stylesheet" href="/assets/index.css"/> <link rel="stylesheet" href="/assets/index.css"/>
</head> </head>
<body class="flex flex-col min-h-screen mx-auto"> <body class="flex flex-col min-h-screen mx-auto bg-gray-100">
<header class="border-b"> <header class="bg-white border-b">
<div class="container flex items-center justify-between px-2 py-4 mx-auto"> <div class="container flex items-center justify-between px-2 py-4 mx-auto">
<a href="<?= route_to('home') ?>" class="text-2xl"><?= isset($page) <a href="<?= route_to('home') ?>" class="text-2xl"><?= isset($page)
? $page->title ? $page->title
...@@ -22,11 +22,15 @@ ...@@ -22,11 +22,15 @@
<main class="container flex-1 px-4 py-10 mx-auto"> <main class="container flex-1 px-4 py-10 mx-auto">
<?= $this->renderSection('content') ?> <?= $this->renderSection('content') ?>
</main> </main>
<footer class="container flex justify-between px-2 py-4 mx-auto text-sm text-right border-t"> <footer class="px-2 py-4 bg-white border-t">
<?= render_page_links() ?> <div class="container flex flex-col items-center justify-between mx-auto text-xs md:flex-row ">
<small><?= lang('Common.powered_by', [ <?= render_page_links('inline-flex mb-4 md:mb-0') ?>
'castopod' => <p class="flex flex-col items-center md:items-end">
'<a class="underline hover:no-underline" href="https://castopod.org/" target="_blank" rel="noreferrer noopener">Castopod</a>', <?= lang('Common.powered_by', [
]) ?></small> 'castopod' =>
</footer> '<a class="underline hover:no-underline" href="https://castopod.org" target="_blank" rel="noreferrer noopener">Castopod</a>',
]) ?>
</p>
</div>
</footer>
</body> </body>
...@@ -5,6 +5,10 @@ $navigation = [ ...@@ -5,6 +5,10 @@ $navigation = [
'icon' => 'mic', 'icon' => 'mic',
'items' => ['podcast-list', 'podcast-create', 'podcast-import'], 'items' => ['podcast-list', 'podcast-create', 'podcast-import'],
], ],
'persons' => [
'icon' => 'folder-user',
'items' => ['person-list', 'person-create'],
],
'users' => ['icon' => 'group', 'items' => ['user-list', 'user-create']], 'users' => ['icon' => 'group', 'items' => ['user-list', 'user-create']],
'pages' => ['icon' => 'pages', 'items' => ['page-list', 'page-create']], 'pages' => ['icon' => 'pages', 'items' => ['page-list', 'page-create']],
]; ?> ]; ?>
......
...@@ -61,6 +61,11 @@ ...@@ -61,6 +61,11 @@
$podcast->id, $podcast->id,
$episode->id $episode->id
) ?>"><?= lang('Episode.edit') ?></a> ) ?>"><?= lang('Episode.edit') ?></a>
<a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to(
'episode-person-manage',
$podcast->id,
$episode->id
) ?>"><?= lang('Person.persons') ?></a>
<a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to( <a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to(
'soundbites-edit', 'soundbites-edit',
$podcast->id, $podcast->id,
......
<?= $this->extend('admin/_layout') ?>
<?= $this->section('title') ?>
<?= lang('Person.episode_form.title') ?>
<?= $this->endSection() ?>
<?= $this->section('pageTitle') ?>
<?= lang('Person.episode_form.title') ?> (<?= count($episodePersons) ?>)
<?= $this->endSection() ?>
<?= $this->section('headerRight') ?>
<?= button(
lang('Person.create'),
route_to('person-create'),
['variant' => 'primary', 'iconLeft' => 'add'],
['class' => 'mr-2']
) ?>
<?= $this->endSection() ?>
<?= $this->section('content') ?>
<?= form_open(route_to('episode-person-edit', $episode->id), [
'method' => 'post',
'class' => 'flex flex-col',
]) ?>
<?= csrf_field() ?>
<?php if ($episodePersons): ?>
<?= form_section(
lang('Person.episode_form.manage_section_title'),
lang('Person.episode_form.manage_section_subtitle')
) ?>
<?= data_table(
[
[
'header' => lang('Person.episode_form.person'),
'cell' => function ($episodePerson) {
return '<div class="flex">' .
'<a href="' .
route_to('person-view', $episodePerson->person->id) .
"\"><img src=\"{$episodePerson->person->image->thumbnail_url}\" alt=\"{$episodePerson->person->full_name}\" class=\"object-cover w-16 h-16 rounded-full\" /></a>" .
'<div class="flex flex-col ml-3">' .
$episodePerson->person->full_name .
($episodePerson->person_group && $episodePerson->person_role
? '<span class="text-sm text-gray-600">' .
lang(
"PersonsTaxonomy.persons.{$episodePerson->person_group}.label"
) .
' ▸ ' .
lang(
"PersonsTaxonomy.persons.{$episodePerson->person_group}.roles.{$episodePerson->person_role}.label"
) .
'</span>'
: '') .
(empty($episodePerson->person->information_url)
? ''
: "<a href=\"{$episodePerson->person->information_url}\" target=\"_blank\" rel=\"noreferrer noopener\" class=\"text-sm text-blue-800 hover:underline\">" .
$episodePerson->person->information_url .
'</a>') .
'</div></div>';
},
],
[
'header' => lang('Common.actions'),
'cell' => function ($episodePerson) {
return button(
lang('Person.episode_form.remove'),
route_to(
'episode-person-remove',
$episodePerson->podcast_id,
$episodePerson->episode_id,
$episodePerson->id
),
['variant' => 'danger', 'size' => 'small']
);
},
],
],
$episodePersons
) ?>
<?= form_section_close() ?>
<?php endif; ?>
<?= form_section(
lang('Person.episode_form.add_section_title'),
lang('Person.episode_form.add_section_subtitle')
) ?>
<?= form_label(
lang('Person.episode_form.person'),
'person',
[],
lang('Person.episode_form.person_hint')
) ?>
<?= form_multiselect('person[]', $personOptions, old('person', []), [
'id' => 'person',
'class' => 'form-select mb-4',
'required' => 'required',
]) ?>
<?= form_label(
lang('Person.episode_form.group_role'),
'group_role',
[],
lang('Person.episode_form.group_role_hint'),
true
) ?>
<?= form_multiselect(
'person_group_role[]',
$taxonomyOptions,
old('person_group_role', []),
['id' => 'person_group_role', 'class' => 'form-select mb-4']
) ?>
<?= form_section_close() ?>
<?= button(
lang('Person.episode_form.submit_add'),
null,
['variant' => 'primary'],
['type' => 'submit', 'class' => 'self-end']
) ?>
<?= form_close() ?>
<?= $this->endSection() ?>
...@@ -64,6 +64,12 @@ ...@@ -64,6 +64,12 @@
['variant' => 'info', 'iconLeft' => 'edit'], ['variant' => 'info', 'iconLeft' => 'edit'],
['class' => 'mb-4'] ['class' => 'mb-4']
) ?> ) ?>
<?= button(
lang('Person.episode_form.title'),
route_to('episode-person-manage', $podcast->id, $episode->id),
['variant' => 'info', 'iconLeft' => 'folder-user'],
['class' => 'mb-4']
) ?>
<?php if (count($episode->soundbites) > 0): ?> <?php if (count($episode->soundbites) > 0): ?>
<?= data_table( <?= data_table(
[ [
......
<?= $this->extend('admin/_layout') ?>
<?= $this->section('title') ?>
<?= lang('Person.create') ?>
<?= $this->endSection() ?>
<?= $this->section('pageTitle') ?>
<?= lang('Person.create') ?>
<?= $this->endSection() ?>
<?= $this->section('content') ?>
<?= form_open_multipart(route_to('person-create'), [
'method' => 'post',
'class' => 'flex flex-col',
]) ?>
<?= csrf_field() ?>
<?= form_section(
lang('Person.form.identity_section_title'),
lang('Person.form.identity_section_subtitle')
) ?>
<?= form_label(
lang('Person.form.full_name'),
'full_name',
[],
lang('Person.form.full_name_hint')
) ?>
<?= form_input([
'id' => 'full_name',
'name' => 'full_name',
'class' => 'form-input mb-4',
'value' => old('full_name'),
'required' => 'required',
'data-slugify' => 'title',
]) ?>
<?= form_label(
lang('Person.form.unique_name'),
'unique_name',
[],
lang('Person.form.unique_name_hint')
) ?>
<?= form_input([
'id' => 'unique_name',
'name' => 'unique_name',
'class' => 'form-input mb-4',
'value' => old('unique_name'),
'required' => 'required',
'data-slugify' => 'slug',
]) ?>
<?= form_label(
lang('Person.form.information_url'),
'information_url',
[],
lang('Person.form.information_url_hint'),
true
) ?>
<?= form_input([
'id' => 'information_url',
'name' => 'information_url',
'class' => 'form-input mb-4',
'value' => old('information_url'),
]) ?>
<?= form_label(lang('Person.form.image'), 'image') ?>
<?= form_input([
'id' => 'image',
'name' => 'image',
'class' => 'form-input',
'required' => 'required',
'type' => 'file',
'accept' => '.jpg,.jpeg,.png',
]) ?>
<small class="mb-4 text-gray-600"><?= lang(
'Person.form.image_size_hint'
) ?></small>
<?= form_section_close() ?>
<?= button(
lang('Person.form.submit_create'),
null,
['variant' => 'primary'],
['type' => 'submit', 'class' => 'self-end']
) ?>
<?= form_close() ?>
<?= $this->endSection() ?>
<?= $this->extend('admin/_layout') ?>
<?= $this->section('title') ?>
<?= lang('Person.edit') ?>
<?= $this->endSection() ?>
<?= $this->section('pageTitle') ?>
<?= lang('Person.edit') ?>
<?= $this->endSection() ?>
<?= $this->section('content') ?>
<?= form_open_multipart(route_to('person-edit', $person->id), [
'method' => 'post',
'class' => 'flex flex-col',
]) ?>
<?= csrf_field() ?>
<?= form_section(
lang('Person.form.identity_section_title'),
lang('Person.form.identity_section_subtitle') .
"<img src=\"{$person->image->thumbnail_url}\" alt=\"{$person->full_name}\" class=\"object-cover w-32 h-32 mt-3 rounded\" />"
) ?>
<?= form_label(
lang('Person.form.full_name'),
'full_name',
[],
lang('Person.form.full_name_hint')
) ?>
<?= form_input([
'id' => 'full_name',
'name' => 'full_name',
'class' => 'form-input mb-4',
'value' => old('full_name', $person->full_name),
'required' => 'required',
'data-slugify' => 'title',
]) ?>
<?= form_label(
lang('Person.form.unique_name'),
'unique_name',
[],
lang('Person.form.unique_name_hint')
) ?>
<?= form_input([
'id' => 'unique_name',
'name' => 'unique_name',
'class' => 'form-input mb-4',
'value' => old('unique_name', $person->unique_name),
'required' => 'required',
'data-slugify' => 'slug',
]) ?>
<?= form_label(
lang('Person.form.information_url'),
'information_url',
[],
lang('Person.form.information_url_hint'),
true
) ?>
<?= form_input([
'id' => 'information_url',
'name' => 'information_url',
'class' => 'form-input mb-4',
'value' => old('information_url', $person->information_url),
]) ?>
<?= form_label(lang('Person.form.image'), 'image') ?>
<?= form_input([
'id' => 'image',
'name' => 'image',
'class' => 'form-input',
'type' => 'file',
'accept' => '.jpg,.jpeg,.png',
]) ?>
<small class="mb-4 text-gray-600"><?= lang(
'Person.form.image_size_hint'
) ?></small>
<?= form_section_close() ?>
<?= button(
lang('Person.form.submit_edit'),
null,
['variant' => 'primary'],
['type' => 'submit', 'class' => 'self-end']
) ?>
<?= form_close() ?>
<?= $this->endSection() ?>
<?= $this->extend('admin/_layout') ?>
<?= $this->section('title') ?>
<?= lang('Person.all_persons') ?>
<?= $this->endSection() ?>
<?= $this->section('pageTitle') ?>
<?= lang('Person.all_persons') ?> (<?= count($persons) ?>)
<?= $this->endSection() ?>
<?= $this->section('headerRight') ?>
<?= button(
lang('Person.create'),
route_to('person-create'),
['variant' => 'primary', 'iconLeft' => 'add'],
['class' => 'mr-2']
) ?>
<?= $this->endSection() ?>
<?= $this->section('content') ?>
<div class="flex flex-wrap">
<?php if (!empty($persons)): ?>
<?php foreach ($persons as $person): ?>
<article class="w-48 h-full mb-4 mr-4 overflow-hidden bg-white border rounded shadow">
<img
alt="<?= $person->full_name ?>"
src="<?= $person->image
->thumbnail_url ?>" class="object-cover w-40 w-full" />
<div class="p-2">
<a href="<?= route_to(
'person-view',
$person->id
) ?>" class="hover:underline">
<h2 class="font-semibold"><?= $person->full_name ?></h2>
</a>
</div>
<footer class="flex items-center justify-end p-2">
<a class="inline-flex p-2 mr-2 text-teal-700 bg-teal-100 rounded-full shadow-xs hover:bg-teal-200" href="<?= route_to(
'person-edit',
$person->id
) ?>" data-toggle="tooltip" data-placement="bottom" title="<?= lang(
'Person.edit'
) ?>"><?= icon('edit') ?></a>
<a class="inline-flex p-2 mr-2 text-gray-700 bg-red-100 rounded-full shadow-xs hover:bg-gray-200" href="<?= route_to(
'person-delete',
$person->id
) ?>" data-toggle="tooltip" data-placement="bottom" title="<?= lang(
'Person.delete'
) ?>"><?= icon('delete-bin') ?></a>
<a class="inline-flex p-2 text-gray-700 bg-gray-100 rounded-full shadow-xs hover:bg-gray-200" href="<?= route_to(
'person-view',
$person->id
) ?>" data-toggle="tooltip" data-placement="bottom" title="<?= lang(
'Person.view'
) ?>"><?= icon('eye') ?></a>
</footer>
</article>
<?php endforeach; ?>
<?php else: ?>
<p class="italic"><?= lang('Person.no_person') ?></p>
<?php endif; ?>
</div>
<?= $this->endSection() ?>
<?= $this->extend('admin/_layout') ?>
<?= $this->section('title') ?>
<?= $person->full_name ?>
<?= $this->endSection() ?>
<?= $this->section('pageTitle') ?>
<?= $person->full_name ?>
<?= $this->endSection() ?>
<?= $this->section('headerRight') ?>
<?= button(
lang('Person.edit'),
route_to('person-edit', $person->id),
['variant' => 'secondary', 'iconLeft' => 'edit'],
['class' => 'mr-2']
) ?>
<?= $this->endSection() ?>
<?= $this->section('content') ?>
<div class="flex flex-wrap">
<div class="w-full max-w-sm mb-6 md:mr-4">
<img
src="<?= $person->image->medium_url ?>"
alt="$person->full_name"
class="object-cover w-full rounded"
/>
</div>
<section class="w-full prose">
<?= $person->full_name ?><br />
<a href="<?= $person->information_url ?>"><?= $person->information_url ?></a>
</section>
</div>
<?= $this->endSection() ?>
...@@ -8,6 +8,10 @@ $podcastNavigation = [ ...@@ -8,6 +8,10 @@ $podcastNavigation = [
'icon' => 'mic', 'icon' => 'mic',
'items' => ['episode-list', 'episode-create'], 'items' => ['episode-list', 'episode-create'],
], ],
'persons' => [
'icon' => 'folder-user',
'items' => ['podcast-person-manage'],
],
'analytics' => [ 'analytics' => [
'icon' => 'line-chart', 'icon' => 'line-chart',
'items' => [ 'items' => [
......
...@@ -58,6 +58,16 @@ ...@@ -58,6 +58,16 @@
$podcast->id, $podcast->id,
$episode->id $episode->id
) ?>"><?= lang('Episode.edit') ?></a> ) ?>"><?= lang('Episode.edit') ?></a>
<a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to(
'episode-person-manage',
$podcast->id,
$episode->id
) ?>"><?= lang('Person.persons') ?></a>
<a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to(
'soundbites-edit',
$podcast->id,
$episode->id
) ?>"><?= lang('Episode.soundbites') ?></a>
<a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to( <a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to(
'episode', 'episode',
$podcast->name, $podcast->name,
......
<?= $this->extend('admin/_layout') ?>
<?= $this->section('title') ?>
<?= lang('Person.podcast_form.title') ?>
<?= $this->endSection() ?>
<?= $this->section('pageTitle') ?>
<?= lang('Person.podcast_form.title') ?> (<?= count($podcastPersons) ?>)
<?= $this->endSection() ?>
<?= $this->section('headerRight') ?>
<?= button(
lang('Person.create'),
route_to('person-create'),
['variant' => 'primary', 'iconLeft' => 'add'],
['class' => 'mr-2']
) ?>
<?= $this->endSection() ?>
<?= $this->section('content') ?>
<?= form_open(route_to('podcast-person-edit', $podcast->id), [
'method' => 'post',
'class' => 'flex flex-col',
]) ?>
<?= csrf_field() ?>
<?php if ($podcastPersons): ?>
<?= form_section(
lang('Person.podcast_form.manage_section_title'),
lang('Person.podcast_form.manage_section_subtitle')
) ?>
<?= data_table(
[
[
'header' => lang('Person.podcast_form.person'),
'cell' => function ($podcastPerson) {
return '<div class="flex">' .
'<a href="' .
route_to('person-view', $podcastPerson->person->id) .
"\"><img src=\"{$podcastPerson->person->image->thumbnail_url}\" alt=\"{$podcastPerson->person->full_name}\" class=\"object-cover w-16 h-16 rounded-full\" /></a>" .
'<div class="flex flex-col ml-3">' .
$podcastPerson->person->full_name .
($podcastPerson->person_group && $podcastPerson->person_role
? '<span class="text-sm text-gray-600">' .
lang(
"PersonsTaxonomy.persons.{$podcastPerson->person_group}.label"
) .
' ▸ ' .
lang(
"PersonsTaxonomy.persons.{$podcastPerson->person_group}.roles.{$podcastPerson->person_role}.label"
) .
'</span>'
: '') .
(empty($podcastPerson->person->information_url)
? ''
: "<a href=\"{$podcastPerson->person->information_url}\" target=\"_blank\" rel=\"noreferrer noopener\" class=\"text-sm text-blue-800 hover:underline\">" .
$podcastPerson->person->information_url .
'</a>') .
'</div></div>';
},
],
[
'header' => lang('Common.actions'),
'cell' => function ($podcastPerson) {
return button(
lang('Person.podcast_form.remove'),
route_to(
'podcast-person-remove',
$podcastPerson->podcast_id,
$podcastPerson->id
),
['variant' => 'danger', 'size' => 'small']
);
},
],
],
$podcastPersons
) ?>
<?= form_section_close() ?>
<?php endif; ?>
<?= form_section(
lang('Person.podcast_form.add_section_title'),
lang('Person.podcast_form.add_section_subtitle')
) ?>
<?= form_label(
lang('Person.podcast_form.person'),
'person',
[],
lang('Person.podcast_form.person_hint')
) ?>
<?= form_multiselect('person[]', $personOptions, old('person', []), [
'id' => 'person',
'class' => 'form-select mb-4',
'required' => 'required',
]) ?>
<?= form_label(
lang('Person.podcast_form.group_role'),
'group_role',
[],
lang('Person.podcast_form.group_role_hint'),
true
) ?>
<?= form_multiselect(
'person_group_role[]',
$taxonomyOptions,
old('person_group_role', []),
['id' => 'person_group_role', 'class' => 'form-select mb-4']
) ?>
<?= form_section_close() ?>
<?= button(
lang('Person.podcast_form.submit_add'),
null,
['variant' => 'primary'],
['type' => 'submit', 'class' => 'self-end']
) ?>
<?= form_close() ?>
<?= $this->endSection() ?>
<?= $this->extend('_layout') ?>
<?= $this->section('title') ?>
<?= lang('Person.credits') ?>
<?= $this->endSection() ?>
<?= $this->section('content') ?>
<div class="grid w-full grid-cols-1 gap-4 md:grid-cols-2">
<?php foreach ($credits as $groupSlug => $groups): ?>
<?php if (
$groupSlug
): ?><div class="col-span-1 mt-12 mb-2 text-xl font-bold text-gray-500 md:text-2xl md:col-span-2 "><?= $groups[
'group_label'
] ?></div><?php endif; ?>
<?php foreach ($groups['persons'] as $personId => $persons): ?>
<div class="flex mt-2 mb-2">
<img src="<?= $persons['thumbnail_url'] ?>" alt="<?= $persons[
'full_name'
] ?>" class="object-cover w-16 h-16 border-4 rounded-full md:h-24 md:w-24 border-gray" />
<div class="flex flex-col ml-3 mr-4"><span class="text-lg font-bold text-gray-700 md:text-xl"><?= $persons[
'full_name'
] ?></span>
<?php if (
!empty($persons['information_url'])
): ?><a href="<?= $persons[
'information_url'
] ?>" class="text-sm text-blue-800 hover:underline" target="_blank" rel="noreferrer noopener"><?= $persons[
'information_url'
] ?></a><?php endif; ?></div>
</div>
<div class="flex flex-col">
<?php foreach ($persons['roles'] as $role_slug => $role_array): ?>
<?= $role_array['role_label'] ?>
<?php foreach ($role_array['is_in'] as $isIn): ?>
<a href="<?= $isIn[
'link'
] ?>" class="text-sm text-gray-500 hover:underline"><?= $isIn[
'title'
] ?></a>
<?php endforeach; ?>
<?php endforeach; ?>
</div>
<?php endforeach; ?>
<?php endforeach; ?>
</div>
<?php $this->endSection(); ?>
...@@ -100,11 +100,28 @@ ...@@ -100,11 +100,28 @@
<?= format_duration($episode->enclosure_duration) ?> <?= format_duration($episode->enclosure_duration) ?>
</time> </time>
</div> </div>
<div class="flex mt-2 mb-1 space-x-2">
<?php foreach ($persons as $person): ?>
<?php if (!empty($person['information_url'])): ?>
<a href="<?= $person[
'information_url'
] ?>" target="_blank" rel="noreferrer noopener">
<?php endif; ?>
<img src="<?= $person['thumbnail_url'] ?>" alt="<?= $person[
'full_name'
] ?>" title="[<?= $person['full_name'] ?>] <?= $person[
'roles'
] ?>" class="object-cover w-12 h-12 rounded-full" />
<?php if (!empty($person['information_url'])): ?>
</a>
<?php endif; ?>
<?php endforeach; ?>
</div>
<?= location_link( <?= location_link(
$episode->location_name, $episode->location_name,
$episode->location_geo, $episode->location_geo,
$episode->location_osmid, $episode->location_osmid,
'self-start mt-2' 'self-start mt-2 mb-2'
) ?> ) ?>
<audio controls preload="none" class="w-full mt-auto"> <audio controls preload="none" class="w-full mt-auto">
<source src="<?= $episode->enclosure_web_url ?>" type="<?= $episode->enclosure_type ?>"> <source src="<?= $episode->enclosure_web_url ?>" type="<?= $episode->enclosure_type ?>">
......