$url)); if (!$d->get_data_to_string()) { header('HTTP/1.0 404 Not Found'); print "Feed Not Found"; logger::fail("PARSE_RSS", " Failed to Download ".$url); exit; } // For debugging // file_put_contents('prefs/temp/feed.xml', $d->get_data()); if ($id) { if (!is_dir('prefs/podcasts/'.$id)) { mkdir('prefs/podcasts/'.$id, 0755); } file_put_contents('prefs/podcasts/'.$id.'/feed.xml', $d->get_data()); } $feed = simplexml_load_string($d->get_data()); logger::trace("PARSE_RSS", " Our LastPubDate is ".$lastpubdate); // Begin RSS Parse $podcast = array(); $podcast['FeedURL'] = $url; $domain = preg_replace('#^(http://.*?)/.*$#', '$1', $url); $ppg = $feed->channel->children('ppg', TRUE); $m = $feed->channel->children('itunes', TRUE); $sy = $feed->channel->children('sy', TRUE); // Automatic Refresh if ($ppg && $ppg->seriesDetails) { switch ($ppg->seriesDetails[0]->attributes()->frequency) { case "hourly": $podcast['RefreshOption'] = REFRESHOPTION_HOURLY; break; case "daily": $podcast['RefreshOption'] = REFRESHOPTION_DAILY; break; case "weekly": $podcast['RefreshOption'] = REFRESHOPTION_WEEKLY; break; case "monthly": $podcast['RefreshOption'] = REFRESHOPTION_MONTHLY; break; default: $podcast['RefreshOption'] = REFRESHOPTION_NEVER; break; } } else if ($sy && $sy->updatePeriod) { switch ($sy->updatePeriod) { case "hourly": $podcast['RefreshOption'] = REFRESHOPTION_HOURLY; break; case "daily": $podcast['RefreshOption'] = REFRESHOPTION_DAILY; break; case "weekly": $podcast['RefreshOption'] = REFRESHOPTION_WEEKLY; break; case "monthly": $podcast['RefreshOption'] = REFRESHOPTION_MONTHLY; break; default: $podcast['RefreshOption'] = REFRESHOPTION_NEVER; break; } } else { $podcast['RefreshOption'] = $prefs['default_podcast_refresh_mode']; } // Episode Expiry if ($ppg && $ppg->seriesDetails && $ppg->seriesDetails[0]->attributes()->daysLive) { $podcast['DaysLive'] = $ppg->seriesDetails[0]->attributes()->daysLive; } else { $podcast['DaysLive'] = -1; } // Image if ($feed->channel->image) { $podcast['Image'] = html_entity_decode($feed->channel->image->url); logger::log("PARSE_RSS", " Image is ".$podcast['Image']); } else if ($m && $m->image) { $podcast['Image'] = $m->image[0]->attributes()->href; logger::log("PARSE_RSS", " Image is ".$podcast['Image']); } else { $podcast['Image'] = "newimages/podcast-logo.svg"; logger::trace("PARSE_RSS", " No Image Found"); } if (preg_match('#^/#', $podcast['Image'])) { // Image link with a relative URL. Duh. $podcast['Image'] = $domain.$image; } // Artist if ($m && $m->author) { $podcast['Artist'] = (string) $m->author; } else { $podcast['Artist'] = ''; } logger::log("PARSE_RSS", " Artist is ".$podcast['Artist']); // Category $cats = array(); if ($m && $m->category) { for ($i = 0; $i < count($m->category); $i++) { $cat = html_entity_decode((string) $m->category[$i]->attributes()->text); if (!in_array($cat, $cats)) { $cats[] = $cat; } } } $spaz = array_diff($cats, array('Podcasts')); natsort($spaz); $podcast['Category'] = implode(', ', $spaz); logger::log("PARSE_RSS", " Category is ".$podcast['Category']); // Title $podcast['Title'] = (string) $feed->channel->title; if ($id !== false) { $albumimage = new baseAlbumImage(array( 'artist' => 'PODCAST', 'albumpath' => $id, 'album' => $podcast['Title'] )); if ($albumimage->get_image_if_exists() === null) { logger::mark("PODCASTS", "Replacing missing podcast image"); download_image($podcast['Image'], $id, $podcast['Title']); } } // Description $podcast['Description'] = (string) $feed->channel->description; // Tracks $podcast['tracks'] = array(); $podcast['LastPubDate'] = $lastpubdate; if ($gettracks) { foreach($feed->channel->item as $item) { $track = array(); $m = $item->children('media', TRUE); // Track Title $track['Title'] = (string) $item->title; logger::log("PARSE_RSS", " Found track ".$track['Title']); // Track URI $uri = null; if ($m && $m->content) { foreach ($m->content as $c) { if (preg_match('/audio/',(string) $c->attributes()->type)) { $uri = (string) $c->attributes()->url; break; } } } if ($item->enclosure && $uri == null) { $uri = (string) $item->enclosure->attributes()->url; } if ($item->link && $uri == null) { $uri = (string) $item->link; } $track['Link'] = $uri; logger::log("PARSE_RSS", " Track URI is ".$uri); if ($item->guid) { $track['GUID'] = $item->guid; } else { $track['GUID'] = $uri; } if ($uri == null) { logger::fail("PARSE_RSS", " Could Not Find URI for track!"); continue; } // Track Duration $track['Duration'] = 0; if ($m && $m->content) { if ($m->content[0]->attributes()->duration) { $track['Duration'] = (string) $m->content[0]->attributes()->duration; } } $m = $item->children('itunes', TRUE); if ($track['Duration'] == 0 && $m && $m->duration) { $track['Duration'] = (string) $m->duration; } if (preg_match('/:/', $track['Duration'])) { $timesplit = explode(':', $track['Duration']); $timefactors = array(1, 60, 3600, 86400); $hms = array_reverse($timesplit); $time = 0; foreach ($hms as $s) { $mf = array_shift($timefactors); if (is_numeric($s)) { $time += ($s * $mf); } else { logger::warn("PARSE_RSS", " Non-numeric duration field encountered in podcast! -",$track['Duration']); $time = 0; break; } } $track['Duration'] = $time; } // Track Author if ($m && $m->author) { $track['Artist'] = (string) $m->author; } else { $track['Artist'] = $podcast['Artist']; } // Track Publication Date $t = strtotime((string) $item->pubDate); logger::log("PARSE_RSS", " Track PubDate is ",(string) $item->pubDate,"(".$t.")"); if ($t === false) { logger::warn("PARSE_RSS", " ERROR - Could not parse episode Publication Date",(string) $item->pubDate); } else if ($t > $podcast['LastPubDate']) { logger::log("PARSE_RSS", " This is a new episode"); } if ($t === false || $podcast['LastPubDate'] === null || $t > $podcast['LastPubDate']) { $podcast['LastPubDate'] = $t; } $track['PubDate'] = $t; if ($item->enclosure && $item->enclosure->attributes()) { $track['FileSize'] = $item->enclosure->attributes()->length; } else { $track['FileSize'] = 0; } if ($m && $m->summary) { $track['Description'] = $m->summary; } else { $track['Description'] = $item->description; } $podcast['tracks'][] = $track; } } if ($lastpubdate !== null) { if ($podcast['LastPubDate'] !== false && $podcast['LastPubDate'] == $lastpubdate) { logger::mark("PARSE_RSS", "Podcast has not been updated since last refresh"); return false; } } return $podcast; } function getNewPodcast($url, $subbed = 1, $gettracks = true) { global $mysqlc, $prefs; logger::mark("PODCASTS", "Getting podcast",$url); $newpodid = null; $podcast = parse_rss_feed($url, false, null, $gettracks); $r = check_if_podcast_is_subscribed(array( 'feedUrl' => $podcast['FeedURL'], 'collectionName' => $podcast['Title'], 'artistName' => $podcast['Artist'])); if (count($r) > 0) { foreach ($r as $a) { logger::fail("PODCASTS", " Already subscribed to podcast",$a['Title']); } header('HTTP/1.0 404 Not Found'); print 'You are already to subscrtibed to '.$podcast['Title']; exit(0); } logger::mark("PODCASTS", "Adding New Podcast",$podcast['Title']); $lastupdate = calculate_best_update_time($podcast); if (sql_prepare_query(true, null, null, null, "INSERT INTO Podcasttable (FeedURL, LastUpdate, Image, Title, Artist, RefreshOption, SortMode, DisplayMode, DaysLive, Description, Version, Subscribed, LastPubDate, Category) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", $podcast['FeedURL'], calculate_best_update_time($podcast), $podcast['Image'], $podcast['Title'], $podcast['Artist'], $podcast['RefreshOption'], $prefs['default_podcast_sort_mode'], $prefs['default_podcast_display_mode'], $podcast['DaysLive'], $podcast['Description'], ROMPR_PODCAST_TABLE_VERSION, $subbed, $podcast['LastPubDate'], $podcast['Category'])) { $newpodid = $mysqlc->lastInsertId(); if (is_dir('prefs/podcasts/'.$newpodid)) { rrmdir('prefs/podcasts/'.$newpodid); } mkdir('prefs/podcasts/'.$newpodid, 0755); download_image($podcast['Image'], $newpodid, $podcast['Title']); if ($subbed == 1) { foreach ($podcast['tracks'] as $track) { if (sql_prepare_query(true, null, null, null, "INSERT INTO PodcastTracktable (PODindex, Title, Artist, Duration, PubDate, FileSize, Description, Link, Guid, New) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", $newpodid, $track['Title'], $track['Artist'], $track['Duration'], $track['PubDate'], $track['FileSize'], $track['Description'], $track['Link'], $track['GUID'], 1)) { logger::log("PODCASTS", " Added Track ".$track['Title']); } else { logger::fail("PODCASTS", " FAILED Adding Track ".$track['Title']); } } } } return $newpodid; } function calculate_best_update_time($podcast) { // Note: this returns a value for LastUpdate, since that is what refresh is based on. // The purpose of this is try to get the refresh in sync with the podcast's publication date. if ($podcast['LastPubDate'] === null) { logger::log("PODCASTS", $podcast['Title'],"last pub date is null"); return time(); } switch ($podcast['RefreshOption']) { case REFRESHOPTION_NEVER: case REFRESHOPTION_HOURLY: case REFRESHOPTION_DAILY: return time(); break; } logger::log("PODCASTS", "Working out best update time for ".$podcast['Title']); $dt = new DateTime(date('c', $podcast['LastPubDate'])); logger::log("PODCASTS", " Last Pub Date is ".$podcast['LastPubDate'].' ('.$dt->format('c').')'); logger::log("PODCASTS", " Podcast Refresh interval is ".$podcast['RefreshOption']); while ($dt->getTimestamp() < time()) { switch ($podcast['RefreshOption']) { case REFRESHOPTION_WEEKLY: $dt->modify('+1 week'); break; case REFRESHOPTION_MONTHLY: $dt->modify('+1 month'); break; default: logger::error("PODCASTS", " Unknown refresh option",$podcast['RefreshOption'],"for podcast ID",$podcast['podid']); return time(); break; } } logger::log("PODCASTS", " Worked out update time based on pubDate and RefreshOption: ".$dt->format('r').' ('.$dt->getTImestamp().')'); logger::log("PODCASTS", " Give it an hour's grace"); $dt->modify('+1 hour'); switch ($podcast['RefreshOption']) { case REFRESHOPTION_WEEKLY: $dt->modify('-1 week'); break; case REFRESHOPTION_MONTHLY: $dt->modify('-1 month'); break; } logger::log("PODCASTS", " Therefore setting lastupdate to: ".$dt->format('r').' ('.$dt->getTImestamp().')'); return $dt->getTimestamp(); } function download_image($url, $podid, $title) { $albumimage = new albumImage(array( 'artist' => 'PODCAST', 'albumpath' => $podid, 'album' => $title, 'source' => $url )); if ($albumimage->get_image_if_exists() === null) { $albumimage->download_image(); $albumimage->update_image_database(); } } function check_podcast_upgrade($podetails, $podid, $podcast) { if ($podetails->Version < ROMPR_PODCAST_TABLE_VERSION) { if ($podcast === false) { logger::blurt("PODCASTS", "Podcast needs to be upgraded, must re-parse the feed"); $podcast = parse_rss_feed($podetails->FeedURL, $podid, null); } upgrade_podcast($podid, $podetails, $podcast); } } function refreshPodcast($podid) { global $prefs; check_refresh_pid(); logger::shout("PODCASTS", "---------------------------------------------------"); logger::shout("PODCASTS", "Refreshing podcast ",$podid); $result = generic_sql_query("SELECT * FROM Podcasttable WHERE PODindex = ".$podid, false, PDO::FETCH_OBJ); if (count($result) > 0) { $podetails = $result[0]; logger::log("PODCASTS", " Podcast title is ".$podetails->Title); } else { logger::error("PODCASTS", "ERROR Looking up podcast ".$podid); return $podid; } $podcast = parse_rss_feed($podetails->FeedURL, $podid, $podetails->LastPubDate); if ($podetails->Subscribed == 1 && $prefs['podcast_mark_new_as_unlistened']) { generic_sql_query("UPDATE PodcastTracktable SET New = 0 WHERE PODindex = ".$podetails->PODindex); } if ($podcast === false) { check_podcast_upgrade($podetails, $podid, $podcast); // Podcast pubDate has not changed, hence we didn't re-parse the feed. // Still calculate the best next update time sql_prepare_query(true, null, null, null, "UPDATE Podcasttable SET LastUpdate = ? WHERE PODindex = ?", calculate_best_update_time( array( 'LastPubDate' => $podetails->LastPubDate, 'RefreshOption' => $podetails->RefreshOption, 'Title' => $podetails->Title ) ), $podid); // Still check to keep (days to keep still needs to be honoured) if (check_tokeep($podetails, $podid) || $prefs['podcast_mark_new_as_unlistened']) { return $podid; } else { return false; } } check_podcast_upgrade($podetails, $podid, $podcast); if ($podetails->Subscribed == 0) { sql_prepare_query(true, null, null, null, "UPDATE Podcasttable SET Description = ?, DaysLive = ?, RefreshOption = ?, LastUpdate = ?, LastPubDate = ? WHERE PODindex = ?", $podcast['Description'], $podcast['DaysLive'], $podcast['RefreshOption'], calculate_best_update_time($podcast), $podcast['LastPubDate'], $podid); sql_prepare_query(true, null, null, null, "UPDATE PodcastTracktable SET New=?, JustUpdated=?, Listened = 0 WHERE PODindex=?", 1, 0, $podid); } else { sql_prepare_query(true, null, null, null, "UPDATE PodcastTracktable SET New=?, JustUpdated=? WHERE PODindex=?", 0, 0, $podid); sql_prepare_query(true, null, null, null, "UPDATE Podcasttable SET Description=?, LastUpdate=?, DaysLive=?, LastPubDate=? WHERE PODindex=?", $podcast['Description'], calculate_best_update_time($podcast), $podcast['DaysLive'], $podcast['LastPubDate'], $podid); } download_image($podcast['Image'], $podid, $podetails->Title); foreach ($podcast['tracks'] as $track) { $trackid = sql_prepare_query(false, null, 'PODTrackindex' , null, "SELECT PODTrackindex FROM PodcastTracktable WHERE Guid=? AND PODindex = ?", $track['GUID'], $podid); if ($trackid !== null) { logger::trace("PODCASTS", " Found existing track ".$track['Title']); sql_prepare_query(true, null, null, null, "UPDATE PodcastTracktable SET JustUpdated=?, Duration=?, Link=? WHERE PODTrackindex=?",1,$track['Duration'], $track['Link'], $trackid); } else { if (sql_prepare_query(true, null, null, null, "INSERT INTO PodcastTracktable (JustUpdated, PODindex, Title, Artist, Duration, PubDate, FileSize, Description, Link, Guid, New) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", 1, $podid, $track['Title'], $track['Artist'], $track['Duration'], $track['PubDate'], $track['FileSize'], $track['Description'], $track['Link'], $track['GUID'], 1)) { logger::log("PODCASTS", " Added Track ".$track['Title']); } else { logger::fail("PODCASTS", " FAILED Adding Track ".$track['Title']); } } } check_tokeep($podetails, $podid); clear_refresh_pid(); return $podid; } function check_tokeep($podetails, $podid) { $retval = false; // Remove tracks that are no longer in the feed and haven't been downloaded if ($podetails->Subscribed == 1) { sql_prepare_query(true, null, null, null, "DELETE FROM PodcastTracktable WHERE PODindex=? AND JustUpdated=? AND Downloaded=?",$podid, 0, 0); // Remove tracks that have been around longer than DaysToKeep - honoring KeepDownloaded if ($podetails->DaysToKeep > 0) { $oldesttime = time() - ($podetails->DaysToKeep * 86400); $numthen = simple_query("COUNT(PODTrackindex)", "PodcastTracktable", 'Deleted = 0 AND PODindex', $podid, 0); $qstring = "UPDATE PodcastTracktable SET Deleted=1 WHERE PODindex = ".$podid." AND PubDate < ".$oldesttime." AND Deleted = 0"; if ($podetails->KeepDownloaded == 1) { $qstring .= " AND Downloaded = 0"; } generic_sql_query($qstring, true); $numnow = simple_query("COUNT(PODTrackindex)", "PodcastTracktable", 'Deleted = 0 AND PODindex', $podid, 0); if ($numnow != $numthen) { logger::log("PODCASTS", " Old episodes were removed from podcast ID ".$podid); $retval = true; } } // Remove tracks where there are more than NumToKeep - honoring KeepDownloaded if ($podetails->NumToKeep > 0) { $getrid = 0; $qstring = "SELECT COUNT(PODTrackindex) AS num FROM PodcastTracktable WHERE PODindex=".$podid." AND Deleted = 0"; if ($podetails->KeepDownloaded == 1) { $qstring .= " AND Downloaded = 0"; } $num = generic_sql_query($qstring, false, null, 'num', 0); $getrid = $num - $podetails->NumToKeep; logger::log("PODCASTS", " Num To Keep is ".$podetails->NumToKeep." and there are ".$num." episodes that can be pruned. Removing ".$getrid); if ($getrid > 0) { $qstring = "SELECT PODTrackindex FROM PodcastTracktable WHERE PODindex=".$podid." AND Deleted = 0"; if ($podetails->KeepDownloaded == 1) { $qstring .= " AND Downloaded=0"; } $qstring .= " ORDER BY PubDate ASC LIMIT ".$getrid; $pods = sql_get_column($qstring, 0); foreach ($pods as $i) { logger::trace("PODCASTS", " Removing Track ".$i); generic_sql_query("UPDATE PodcastTracktable SET Deleted=1 WHERE PODTrackindex=".$i, true); $retval = true; } } } } return $retval; } function upgrade_podcast($podid, $podetails, $podcast) { $v = $podetails->Version; while ($v < ROMPR_PODCAST_TABLE_VERSION) { switch ($v) { case 1: logger::mark("PODCASTS", "Updating Podcast ".$podetails->Title." to version 2"); foreach ($podcast['tracks'] as $track) { $t = sql_prepare_query(false, PDO::FETCH_OBJ, null, null, "SELECT * FROM PodcastTracktable WHERE Link=? OR OrigLink=?", $track['Link'], $track['Link']); foreach($t as $result) { logger::log("PODCASTS", " Updating Track ".$result->Title); logger::trace("PODCASTS", " GUID is ".$track['GUID']); $dlfilename = null; if ($result->Downloaded == 1) { $dlfilename = basename($result->Link); logger::log("PODCASTS", " Track has been downloaded to ".$dlfilename); } sql_prepare_query(true, null, null, null, "UPDATE PodcastTracktable SET Link = ?, Guid = ?, Localfilename = ?, OrigLink = NULL WHERE PODTrackindex = ?", $track['Link'], $track['GUID'], $dlfilename, $result->PODTrackindex); } } generic_sql_query("UPDATE Podcasttable SET Version = 2 WHERE PODindex = ".$podid, true); $v++; break; case 2: // This will have been done by the function below $v++; break; case 3: logger::mark("PODCASTS", "Updating Podcast ".$podetails->Title." to version 4"); sql_prepare_query(true, null, null, null, "UPDATE Podcasttable SET Version = ?, Category = ? WHERE PODindex = ?", 4, $podcast['Category'], $podid); $v++; break; } } } function upgrade_podcasts_to_version() { $pods = generic_sql_query('SELECT * FROM Podcasttable WHERE Subscribed = 1 AND Version < '.ROMPR_PODCAST_TABLE_VERSION); foreach ($pods as $podcast) { $v = $podcast['Version']; while ($v < ROMPR_PODCAST_TABLE_VERSION) { switch ($v) { case 2; logger::mark("PODCASTS", " Updating Podcast ".$podcast['Title']." to version 3"); $newest_track = generic_sql_query("SELECT PubDate FROM PodcastTracktable WHERE PODindex = ".$podcast['PODindex']." ORDER BY PubDate DESC LIMIT 1"); $podcast['LastPubDate'] = $newest_track[0]['PubDate']; logger::log("PODCASTS", " Last episode for this podcast was published on ".date('c', $podcast['LastPubDate'])); switch($podcast['RefreshOption']) { case REFRESHOPTION_WEEKLY: case REFRESHOPTION_MONTHLY: $podcast['LastUpdate'] = calculate_best_update_time($podcast); break; } generic_sql_query("UPDATE Podcasttable SET LastUpdate = ".$podcast['LastUpdate'].", LastPubDate = ".$podcast['LastPubDate'].", Version = 3 WHERE PODindex = ".$podcast['PODindex']); $v++; break; case 3; // Upgrade to version 4 can only happen after feed has been re-parsed $v++; break; } } } } function outputPodcast($podid, $do_searchbox = true) { $result = generic_sql_query("SELECT * FROM Podcasttable WHERE PODindex = ".$podid, false, PDO::FETCH_OBJ); foreach ($result as $obj) { doPodcast($obj, $do_searchbox); } } function doPodcast($y, $do_searchbox) { if ($y->Subscribed == 0) { logger::mark("PODCASTS", "Getting feed for unsubscribed podcast ".$y->FeedURL); refreshPodcast($y->PODindex); $a = generic_sql_query("SELECT * FROM Podcasttable WHERE PODindex = ".$y->PODindex, false, PDO::FETCH_OBJ); if (count($a) > 0) { $y = $a[0]; } else { logger::fail("PODCASTS", "ERROR looking up podcast",$y->FeedURL); return; } } $aa = $y->Artist; if ($aa != '') { $aa = $aa . ' - '; } $pm = $y->PODindex; trackControlHeader('','','podcast_'. $pm,array(array('Image' => $y->Image))); print '
'.format_text($y->Description).'
'; if ($y->Subscribed == 1) { print '
'; print ''; print ''; print ''; print ''; print ''; print ''; print '
'; print ''; print '
'; print '