Source of file Parser.php

Size: 12,700 Bytes - Last Modified: 2020-12-04T01:41:26+00:00

/home/vagrant/Code/projects/podcast-feed-parser/src/Parser.php

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
<?php

namespace Lukaswhite\PodcastFeedParser;

use Lukaswhite\PodcastFeedParser\Exceptions\FileNotFoundException;
use Lukaswhite\PodcastFeedParser\Exceptions\InvalidXmlException;
use Lukaswhite\PodcastFeedParser\Rawvoice\Subscribe;

/**
 * Class Parser
 *
 * Parse a podcast feed.
 *
 * @package Lukaswhite\PodcastFeedParser
 */
class Parser
{
    /**
     * Class constants for the various namespaces
     */
    const NS_ITUNES = 'http://www.itunes.com/dtds/podcast-1.0.dtd';
    const NS_GOOGLE_PLAY = 'http://www.google.com/schemas/play-podcasts/1.0';
    const NS_ATOM = 'http://www.w3.org/2005/Atom';
    const NS_SYNDICATION = 'http://purl.org/rss/1.0/modules/syndication/';
    const NS_RAWVOICE = 'http://www.rawvoice.com/rawvoiceRssModule/';

    /**
     * The raw feed content
     *
     * @var string
     */
    protected $content;

    /**
     * @var \SimplePie
     */
    protected $sp;

    /**
     * @param string $content
     * @return $this
     * @throws InvalidXmlException
     */
    public function setContent(string $content): self
    {
        try {
            simplexml_load_string($content);
        } catch (\Exception $e) {
            throw new InvalidXmlException('The feed does not appear to be valid XML');
        }

        $this->content = $content;
        return $this;
    }

    /**
     * @param string $filepath
     * @return self
     * @throws FileNotFoundException
     * @throws InvalidXmlException
     */
    public function load(string $filepath): self
    {
        if (!file_exists($filepath)) {
            throw new FileNotFoundException('The file could not be found');
        }
        $this->setContent(file_get_contents($filepath));
        return $this;
    }

    /**
     * Run the parser and return an object that represents the parsed podcast.
     *
     * @return Podcast
     * @throws \Exception
     */
    public function run(): Podcast
    {
        $this->sp = new \SimplePie();
        $this->sp->set_raw_data($this->content);
        $this->sp->init();

        $podcast = new Podcast();
        $podcast->setTitle($this->sp->get_title())
            ->setDescription($this->sp->get_description())
            ->setLanguage($this->sp->get_language())
            ->setCopyright($this->sp->get_copyright())
            ->setLink($this->sp->get_link());

        $this->parseRssTags($podcast);
        $this->parseAtomTags($podcast);
        $this->parseSyndicationFields($podcast);
        $this->parseRawvoiceFields($podcast);

        if ($this->sp->get_author()) {
            $podcast->setAuthor($this->sp->get_author()->get_name());
        }

        $iTunesType = $this->sp->get_channel_tags(self::NS_ITUNES, 'type');
        if ($iTunesType && count($iTunesType)) {
            $podcast->setType($iTunesType[0]['data']);
        }

        $editor = $this->sp->get_channel_tags('', 'managingEditor');
        if ($editor && count($editor)) {
            $podcast->setManagingEditor($editor[0]['data']);
        }

        if ( $this->getSingleNamespacedChannelItem(self::NS_ITUNES, 'subtitle')) {
            $podcast->setSubtitle(
                $this->getSingleNamespacedChannelItem(self::NS_ITUNES, 'subtitle')['data']
            );
        }

        if ( $this->getSingleNamespacedChannelItem(self::NS_ITUNES, 'explicit')) {
            $podcast->setExplicit(
                $this->getSingleNamespacedChannelItem(self::NS_ITUNES, 'explicit')['data']
            );
        }

        if ( $this->getSingleNamespacedChannelItem(self::NS_ITUNES, 'new-feed-url')) {
            $podcast->setNewFeedUrl(
                $this->getSingleNamespacedChannelItem(self::NS_ITUNES, 'new-feed-url')['data']
            );
        }

        $image = $this->getSingleNamespacedChannelItem(self::NS_ITUNES, 'image');
        if ( $image ) {
            $artwork = new Artwork();
            $artwork->setUri(
                $this->getSingleNamespacedChannelItem(self::NS_ITUNES, 'image')['attribs']['']['href']
            );
            $podcast->setArtwork($artwork);
        }

        if ( $this->getSingleNamespacedChannelItem(self::NS_ITUNES, 'owner')) {
            $ownerData = $this->getSingleNamespacedChannelItem(self::NS_ITUNES, 'owner');
            $owner = new Owner();
            if (isset($ownerData['child'])&&
                isset($ownerData['child'][self::NS_ITUNES])&&
                isset($ownerData['child'][self::NS_ITUNES]['name'])) {
                $owner->setName($ownerData['child'][self::NS_ITUNES]['name'][0]['data']);
            }
            if (isset($ownerData['child'])&&
                isset($ownerData['child'][self::NS_ITUNES])&&
                isset($ownerData['child'][self::NS_ITUNES]['email'])) {
                $owner->setEmail($ownerData['child'][self::NS_ITUNES]['email'][0]['data']);
            }
            $podcast->setOwner($owner);
        }

        $itunesCategories = $this->sp->get_channel_tags(self::NS_ITUNES, 'category');
        if ($itunesCategories && count($itunesCategories)) {
            foreach($itunesCategories as $categoryData) {
                $category = new Category();
                $category->setType(Category::ITUNES)
                    ->setName($categoryData['attribs']['']['text']);
                if(isset($categoryData['child'])&&is_array($categoryData['child'])) {
                    foreach($categoryData['child'][self::NS_ITUNES]['category'] as $subCategoryData) {
                        $category->addSubCategory(
                            ( new Category() )
                                ->setType(Category::ITUNES)
                                ->setName($subCategoryData['attribs']['']['text'])
                        );
                    }
                }
                $podcast->addCategory($category);
            }
        }

        $googlePlayCategories = $this->sp->get_channel_tags(self::NS_GOOGLE_PLAY, 'category');
        if ($googlePlayCategories && count($googlePlayCategories)) {
            foreach($googlePlayCategories as $categoryData) {
                $category = new Category();
                $category->setType(Category::GOOGLE_PLAY)
                    ->setName($categoryData['attribs']['']['text']);
                $podcast->addCategory($category);
            }
        }

        // Now add the episodes
        foreach ($this->sp->get_items() as $item) {
            $podcast->addEpisode($this->parseEpisodeItem($item));
        }

        return $podcast;
    }

    /**
     * @param Podcast $podcast
     * @throws \Exception
     */
    protected function parseRssTags(Podcast $podcast)
    {
        $generator = $this->sp->get_channel_tags('', 'generator');
        if ($generator && count($generator)) {
            $podcast->setGenerator($generator[0]['data']);
        }

        $lastBuildDate = $this->sp->get_channel_tags('', 'lastBuildDate');
        if ($lastBuildDate && count($lastBuildDate)) {
            $podcast->setLastBuildDate((new \DateTime())->setTimestamp(strtotime($lastBuildDate[0]['data'])));
        }
    }

    /**
     * @param Podcast $podcast
     */
    protected function parseAtomTags(Podcast $podcast)
    {
        $atomLinks = $this->sp->get_channel_tags(self::NS_ATOM, 'link');
        if($atomLinks && count($atomLinks)) {
            foreach ($atomLinks as $atomLink) {
                $link = new Link($atomLink['attribs']['']['href']);
                if (isset($atomLink['attribs']['']['rel'])) {
                    $link->setRel($atomLink['attribs']['']['rel']);
                }
                if (isset($atomLink['attribs']['']['type'])) {
                    $link->setType($atomLink['attribs']['']['type']);
                }
                $podcast->addAtomLink($link);
            }
        }
    }

    /**
     * @param Podcast $podcast
     * @throws \Exception
     */
    protected function parseSyndicationFields(Podcast $podcast)
    {
        $updatePeriod = $this->sp->get_channel_tags(self::NS_SYNDICATION, 'updatePeriod');
        if($updatePeriod&&count($updatePeriod)) {
            $podcast->setUpdatePeriod($updatePeriod[0]['data']);
        }
        $updateFrequency = $this->sp->get_channel_tags(self::NS_SYNDICATION, 'updateFrequency');
        if($updateFrequency&&count($updateFrequency)) {
            $podcast->setUpdateFrequency(intval($updateFrequency[0]['data']));
        }
        $updateBase = $this->sp->get_channel_tags(self::NS_SYNDICATION, 'updateBase');
        if($updateBase&&count($updateBase)) {
            $podcast->setUpdateBase((new \DateTime())->setTimestamp(strtotime($updateBase[0]['data'])));
        }
    }

    /**
     * @param Podcast $podcast
     * @throws \Exception
     */
    protected function parseRawvoiceFields(Podcast $podcast)
    {
        $rating = $this->sp->get_channel_tags(self::NS_RAWVOICE, 'rating');
        if($rating&&count($rating)) {
            $podcast->setRawvoiceRating($rating[0]['data']);
        }
        $location = $this->sp->get_channel_tags(self::NS_RAWVOICE, 'location');
        if($location&&count($location)) {
            $podcast->setRawvoiceLocation($location[0]['data']);
        }
        $frequency = $this->sp->get_channel_tags(self::NS_RAWVOICE, 'frequency');
        if($frequency&&count($frequency)) {
            $podcast->setRawvoiceFrequency($frequency[0]['data']);
        }
        $subscribe = $this->sp->get_channel_tags(self::NS_RAWVOICE, 'subscribe');
        if($subscribe&&count($subscribe)) {
            $links = new Subscribe();
            foreach($subscribe[0]['attribs'][''] as $platform => $link) {
                $links->addLink($platform,$link);
            }
            $podcast->setRawvoiceSubscribe($links);
        }
    }

    /**
     * @param \SimplePie_Item $item
     * @return Episode
     * @throws \Exception
     */
    protected function parseEpisodeItem(\SimplePie_Item $item)
    {
        $episode = new Episode();
        $episode->setTitle($item->get_title())
            ->setDescription($item->get_description())
            ->setLink($item->get_link())
            ->setPublishedDate(new \DateTime($item->get_date()));

        $guid = $item->get_item_tags('', 'guid');
        if ($guid && count($guid)) {
            $episode->setGuid($guid[0]['data']);
        }

        $subtitle = $item->get_item_tags(self::NS_ITUNES, 'subtitle');
        if ($subtitle && count($subtitle)) {
            $episode->setSubtitle($subtitle[0]['data']);
        }

        $explicit = $item->get_item_tags(self::NS_ITUNES, 'explicit');
        if ( $explicit && count($explicit)) {
            $episode->setExplicit($explicit[0]['data']);
        }

        $episodeNumber = $item->get_item_tags(self::NS_ITUNES, 'episode');
        if ( $episodeNumber && count($episodeNumber)) {
            $episode->setEpisodeNumber(intval($episodeNumber[0]['data']));
        }

        $season = $item->get_item_tags(self::NS_ITUNES, 'season');
        if ( $season && count($season)) {
            $episode->setSeason(intval($season[0]['data']));
        }

        $episodeType = $item->get_item_tags(self::NS_ITUNES, 'episodeType');
        if ( $episodeType && count($episodeType)) {
            $episode->setType($episodeType[0]['data']);
        }

        $image = $item->get_item_tags(self::NS_ITUNES, 'image');
        if ( $image && count($image)) {
            $artwork = new Artwork();
            $artwork->setUri(
                $image[0]['attribs']['']['href']
            );
            $episode->setArtwork($artwork);
        }

        $enclosure = $item->get_enclosure();
        if ( $enclosure && $enclosure->get_link()) {
            $media = new Media();
            $media->setUri($enclosure->get_link())
                ->setMimeType($enclosure->get_type())
                ->setLength($enclosure->get_length());

            $episode->setMedia($this->getFile($item));
        }

        return $episode;
    }

    /**
     * @param \SimplePie_Item $item
     * @return Media
     */
    protected function getFile(\SimplePie_Item $item): Media
    {
        $enclosure = $item->get_enclosure();
        $media = new Media();
        $media->setUri($enclosure->get_link())
            ->setMimeType($enclosure->get_type())
            ->setLength($enclosure->get_length());
        return $media;
    }

    /**
     * @param $namespace
     * @param $name
     * @param null $item
     * @return mixed
     */
    protected function getSingleNamespacedChannelItem($namespace, $name, $item = null )
    {
        $items = $this->sp->get_channel_tags($namespace, $name);
        if ( $items && count( $items ) ) {
            return $items[0];
        }
    }


}