+ */
+ public function getEpisodes(): Collection
+ {
+ return $this->episodes;
+ }
+
+ public function addEpisode(Episode $episode): self
+ {
+ if (!$this->episodes->contains($episode)) {
+ $this->episodes->add($episode);
+ $episode->setPodcast($this);
+ }
+
+ return $this;
+ }
+
+ public function removeEpisode(Episode $episode): self
+ {
+ if ($this->episodes->removeElement($episode)) {
+ // set the owning side to null (unless already changed)
+ if ($episode->getPodcast() === $this) {
+ $episode->setPodcast(null);
+ }
+ }
+
+ return $this;
+ }
+
+ public function getWebsite(): string
+ {
+ return $this->website;
+ }
+
+ public function setWebsite(string $website): self
+ {
+ $this->website = $website;
+
+ return $this;
+ }
+
+ public function getDescription(): string
+ {
+ return $this->description;
+ }
+
+ public function setDescription(string $description): self
+ {
+ $this->description = $description;
+
+ return $this;
+ }
+
+ public function getAuthor(): string
+ {
+ return $this->author;
+ }
+
+ public function setAuthor(string $authorName): self
+ {
+ $this->author = $authorName;
+
+ return $this;
+ }
+
+ public function getEmail(): string
+ {
+ return $this->email;
+ }
+
+ public function setEmail(string $email): self
+ {
+ $this->email = $email;
+
+ return $this;
+ }
+
+ public function getLogoFilename(): string
+ {
+ return $this->logoFilename;
+ }
+
+ public function setLogoFilename(string $logoFilename): self
+ {
+ $this->logoFilename = $logoFilename;
+
+ return $this;
+ }
+}
diff --git a/src/Repository/EpisodeRepository.php b/src/Repository/EpisodeRepository.php
new file mode 100644
index 0000000..59c5196
--- /dev/null
+++ b/src/Repository/EpisodeRepository.php
@@ -0,0 +1,66 @@
+
+ *
+ * @method Episode|null find($id, $lockMode = null, $lockVersion = null)
+ * @method Episode|null findOneBy(array $criteria, array $orderBy = null)
+ * @method Episode[] findAll()
+ * @method Episode[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
+ */
+class EpisodeRepository extends ServiceEntityRepository
+{
+ public function __construct(ManagerRegistry $registry)
+ {
+ parent::__construct($registry, Episode::class);
+ }
+
+ public function save(Episode $entity, bool $flush = false): void
+ {
+ $this->getEntityManager()->persist($entity);
+
+ if ($flush) {
+ $this->getEntityManager()->flush();
+ }
+ }
+
+ public function remove(Episode $entity, bool $flush = false): void
+ {
+ $this->getEntityManager()->remove($entity);
+
+ if ($flush) {
+ $this->getEntityManager()->flush();
+ }
+ }
+
+// /**
+// * @return Episode[] Returns an array of Episode objects
+// */
+// public function findByExampleField($value): array
+// {
+// return $this->createQueryBuilder('e')
+// ->andWhere('e.exampleField = :val')
+// ->setParameter('val', $value)
+// ->orderBy('e.id', 'ASC')
+// ->setMaxResults(10)
+// ->getQuery()
+// ->getResult()
+// ;
+// }
+
+// public function findOneBySomeField($value): ?Episode
+// {
+// return $this->createQueryBuilder('e')
+// ->andWhere('e.exampleField = :val')
+// ->setParameter('val', $value)
+// ->getQuery()
+// ->getOneOrNullResult()
+// ;
+// }
+}
diff --git a/src/Repository/PodcastRepository.php b/src/Repository/PodcastRepository.php
new file mode 100644
index 0000000..d29dcf6
--- /dev/null
+++ b/src/Repository/PodcastRepository.php
@@ -0,0 +1,66 @@
+
+ *
+ * @method Podcast|null find($id, $lockMode = null, $lockVersion = null)
+ * @method Podcast|null findOneBy(array $criteria, array $orderBy = null)
+ * @method Podcast[] findAll()
+ * @method Podcast[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
+ */
+class PodcastRepository extends ServiceEntityRepository
+{
+ public function __construct(ManagerRegistry $registry)
+ {
+ parent::__construct($registry, Podcast::class);
+ }
+
+ public function save(Podcast $entity, bool $flush = false): void
+ {
+ $this->getEntityManager()->persist($entity);
+
+ if ($flush) {
+ $this->getEntityManager()->flush();
+ }
+ }
+
+ public function remove(Podcast $entity, bool $flush = false): void
+ {
+ $this->getEntityManager()->remove($entity);
+
+ if ($flush) {
+ $this->getEntityManager()->flush();
+ }
+ }
+
+// /**
+// * @return Podcast[] Returns an array of Podcast objects
+// */
+// public function findByExampleField($value): array
+// {
+// return $this->createQueryBuilder('p')
+// ->andWhere('p.exampleField = :val')
+// ->setParameter('val', $value)
+// ->orderBy('p.id', 'ASC')
+// ->setMaxResults(10)
+// ->getQuery()
+// ->getResult()
+// ;
+// }
+
+// public function findOneBySomeField($value): ?Podcast
+// {
+// return $this->createQueryBuilder('p')
+// ->andWhere('p.exampleField = :val')
+// ->setParameter('val', $value)
+// ->getQuery()
+// ->getOneOrNullResult()
+// ;
+// }
+}
diff --git a/src/Service/FeedService.php b/src/Service/FeedService.php
new file mode 100644
index 0000000..d6d96ea
--- /dev/null
+++ b/src/Service/FeedService.php
@@ -0,0 +1,81 @@
+entityManager = $entityManager;
+ $this->parameterBag = $parameterBag;
+ $this->baseFeedUrl = $urlGenerator->generate(
+ 'app_index',
+ [],
+ UrlGeneratorInterface::ABSOLUTE_URL
+ ) . Constants::FILES_BASE_PATH;
+ }
+
+ public const DOCUMENT_VERSION = '2.0';
+
+ public function generate(Podcast $podcast): string|false
+ {
+ $document = new DOMDocument();
+ $rssElement = new DOMElement('rss');
+ $document->appendChild($rssElement);
+ $rssElement->setAttribute('version', $this::DOCUMENT_VERSION);
+ $channelElement = new DOMElement('channel');
+ $rssElement->appendChild($channelElement);
+ $titleElement = new DOMElement('title');
+ $channelElement->appendChild($titleElement);
+ $titleElement->appendChild(new DOMText($podcast->getName()));
+ $channelElement->appendChild(new DOMElement('description', $podcast->getDescription()));
+ $channelElement->appendChild(new DOMElement('link', $podcast->getWebsite()));
+
+ $episodes = $this->entityManager->createQuery(
+ 'SELECT e FROM App\Entity\Episode e'
+ .' WHERE e.podcast = :podcastId'
+ .' AND e.audioFilename IS NOT NULL'
+ )->setParameter('podcastId', $podcast->getId())->getResult();
+ foreach ($episodes as $episode) {
+ $audioUrl = $this->baseFeedUrl . $episode->getAudioFilename(); // Also used as ID.
+ $itemElement = new DOMElement('item');
+ $channelElement->appendChild($itemElement);
+ $titleElement = new DOMElement('title');
+ $itemElement->appendChild($titleElement);
+ $titleElement->appendChild(new DOMText($episode->getTitle()));
+ $itemElement->appendChild(new DOMElement('description', $episode->getDescription()));
+ $itemElement->appendChild(new DOMElement('link', $audioUrl));
+ $itemElement->appendChild(new DOMElement('guid', $audioUrl));
+ $enclosureElement = new DOMElement('enclosure');
+ $itemElement->appendChild($enclosureElement);
+ $enclosureElement->setAttribute('url', $audioUrl);
+ $filepath =
+ $this->parameterBag->get('kernel.project_dir')
+ . '/'
+ . Constants::BASE_PUBLIC_DIR
+ . Constants::FILES_BASE_PATH
+ . $episode->getAudioFilename();
+ $enclosureElement->setAttribute('type', mime_content_type($filepath));
+ $enclosureElement->setAttribute('length', (string) filesize($filepath));
+ }
+
+ return $document->saveXML();
+ }
+}
diff --git a/symfony.lock b/symfony.lock
index 5ef3ac8..caf600c 100644
--- a/symfony.lock
+++ b/symfony.lock
@@ -35,6 +35,15 @@
"migrations/.gitignore"
]
},
+ "easycorp/easyadmin-bundle": {
+ "version": "4.6",
+ "recipe": {
+ "repo": "github.com/symfony/recipes",
+ "branch": "main",
+ "version": "3.0",
+ "ref": "b131e6cbfe1b898a508987851963fff485986285"
+ }
+ },
"phpunit/phpunit": {
"version": "9.6",
"recipe": {
@@ -239,6 +248,18 @@
"templates/base.html.twig"
]
},
+ "symfony/uid": {
+ "version": "6.2",
+ "recipe": {
+ "repo": "github.com/symfony/recipes",
+ "branch": "main",
+ "version": "6.2",
+ "ref": "d294ad4add3e15d7eb1bae0221588ca89b38e558"
+ },
+ "files": [
+ "config/packages/uid.yaml"
+ ]
+ },
"symfony/validator": {
"version": "6.2",
"recipe": {
diff --git a/templates/404.html.twig b/templates/404.html.twig
new file mode 100644
index 0000000..ba6cda0
--- /dev/null
+++ b/templates/404.html.twig
@@ -0,0 +1,6 @@
+{% extends "base.html.twig" %}
+
+{% block body %}
+ 404
+ Page not found. 🤔🔎
+{% endblock %}
diff --git a/templates/500.html.twig b/templates/500.html.twig
new file mode 100644
index 0000000..719e26f
--- /dev/null
+++ b/templates/500.html.twig
@@ -0,0 +1,6 @@
+{% extends "base.html.twig" %}
+
+{% block body %}
+ 500
+ An error occured, sorry! 😭
+{% endblock %}
diff --git a/templates/admin/field/file.html.twig b/templates/admin/field/file.html.twig
new file mode 100644
index 0000000..4a3c780
--- /dev/null
+++ b/templates/admin/field/file.html.twig
@@ -0,0 +1,11 @@
+{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #}
+{# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #}
+{# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #}
+{% set files = field.formattedValue %}
+{% if files is not iterable %}
+ {% set files = [files] %}
+{% endif %}
+
+{% for file in files %}
+ {{ file }}
+{% endfor %}
diff --git a/templates/base.html.twig b/templates/base.html.twig
index d4f83f7..bf1895f 100644
--- a/templates/base.html.twig
+++ b/templates/base.html.twig
@@ -2,13 +2,16 @@
- {% block title %}Welcome!{% endblock %}
+
+ {% block title %}LSBC{% endblock %}
{# Run `composer require symfony/webpack-encore-bundle` to start using Symfony UX #}
{% block stylesheets %}
+
{{ encore_entry_link_tags('app') }}
{% endblock %}
-
{% block javascripts %}
{{ encore_entry_script_tags('app') }}
{% endblock %}
diff --git a/templates/feed/index.html.twig b/templates/feed/index.html.twig
new file mode 100644
index 0000000..1837e8e
--- /dev/null
+++ b/templates/feed/index.html.twig
@@ -0,0 +1,10 @@
+{% extends 'base.html.twig' %}
+
+{% block body %}
+ Podcasts available on this LSBC instance:
+
+{% endblock %}
diff --git a/templates/feed/podcast.html.twig b/templates/feed/podcast.html.twig
new file mode 100644
index 0000000..dd3e0ec
--- /dev/null
+++ b/templates/feed/podcast.html.twig
@@ -0,0 +1,11 @@
+{% extends 'base.html.twig' %}
+
+{% block body %}
+ {{ podcast.name }}
+
+ By {{ podcast.author }}.
+ Contact: {{ podcast.email }}
+ {% set feed = url('app_podcast_feed', {slug: podcast.slug}) %}
+ Feed URL: {{ feed }}
+
+{% endblock %}