summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/jetpack/_inc/lib/class-jetpack-podcast-feed-locator.php')
-rw-r--r--plugins/jetpack/_inc/lib/class-jetpack-podcast-feed-locator.php113
1 files changed, 113 insertions, 0 deletions
diff --git a/plugins/jetpack/_inc/lib/class-jetpack-podcast-feed-locator.php b/plugins/jetpack/_inc/lib/class-jetpack-podcast-feed-locator.php
new file mode 100644
index 00000000..82a2a766
--- /dev/null
+++ b/plugins/jetpack/_inc/lib/class-jetpack-podcast-feed-locator.php
@@ -0,0 +1,113 @@
+<?php
+/**
+ * Extension of the SimplePie_Locator class, to detect podcast feeds
+ *
+ * @package automattic/jetpack
+ */
+
+/**
+ * Class Jetpack_Podcast_Feed_Locator
+ */
+class Jetpack_Podcast_Feed_Locator extends SimplePie_Locator {
+ /**
+ * Overrides the locator is_feed function to check for
+ * appropriate podcast elements.
+ *
+ * @param SimplePie_File $file The file being checked.
+ * @param boolean $check_html Adds text/html to the mimetypes checked.
+ */
+ public function is_feed( $file, $check_html = false ) {
+ return parent::is_feed( $file, $check_html ) && $this->is_podcast_feed( $file );
+ }
+
+ /**
+ * Checks the contents of the file for elements that make
+ * it a podcast feed.
+ *
+ * @param SimplePie_File $file The file being checked.
+ */
+ private function is_podcast_feed( $file ) {
+ // If we can't read the DOM assume it's a podcast feed, we'll work
+ // it out later.
+ if ( ! class_exists( 'DOMDocument' ) ) {
+ return true;
+ }
+
+ $feed_dom = $this->safely_load_xml( $file->body );
+
+ // Do this as either/or but prioritise the itunes namespace. It's pretty likely
+ // that it's a podcast feed we've found if that namespace is present.
+ return $feed_dom && $this->has_itunes_ns( $feed_dom ) && $this->has_audio_enclosures( $feed_dom );
+ }
+
+ /**
+ * Safely loads an XML file
+ *
+ * @param string $xml A string of XML to load.
+ * @return DOMDocument|false A restulting DOM document or `false` if there is an error.
+ */
+ private function safely_load_xml( $xml ) {
+ $disable_entity_loader = PHP_VERSION_ID < 80000;
+
+ if ( $disable_entity_loader && ! function_exists( 'libxml_disable_entity_loader' ) ) {
+ return false;
+ }
+
+ if ( $disable_entity_loader ) {
+ // This function has been deprecated in PHP 8.0 because in libxml 2.9.0, external entity loading
+ // is disabled by default, so this function is no longer needed to protect against XXE attacks.
+ // phpcs:ignore Generic.PHP.DeprecatedFunctions.Deprecated, PHPCompatibility.FunctionUse.RemovedFunctions.libxml_disable_entity_loaderDeprecated
+ $loader = libxml_disable_entity_loader( true );
+ }
+
+ $errors = libxml_use_internal_errors( true );
+
+ $return = new DOMDocument();
+ if ( ! $return->loadXML( $xml ) ) {
+ return false;
+ }
+
+ libxml_use_internal_errors( $errors );
+
+ if ( $disable_entity_loader && isset( $loader ) ) {
+ // phpcs:ignore Generic.PHP.DeprecatedFunctions.Deprecated, PHPCompatibility.FunctionUse.RemovedFunctions.libxml_disable_entity_loaderDeprecated
+ libxml_disable_entity_loader( $loader );
+ }
+
+ return $return;
+ }
+
+ /**
+ * Checks the RSS feed for the presence of the itunes podcast namespace.
+ * It's pretty loose and just checks the URI for itunes.com
+ *
+ * @param DOMDocument $dom The XML document to check.
+ * @return boolean Whether the itunes namespace is defined.
+ */
+ private function has_itunes_ns( $dom ) {
+ $xpath = new DOMXPath( $dom );
+ foreach ( $xpath->query( 'namespace::*' ) as $node ) {
+ // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+ // nodeValue is not valid, but it's part of the DOM API that we don't control.
+ if ( strstr( $node->nodeValue, 'itunes.com' ) ) {
+ return true;
+ }
+ // phpcs:enable
+ }
+ return false;
+ }
+
+ /**
+ * Checks the RSS feed for the presence of enclosures with an audio mimetype.
+ *
+ * @param DOMDocument $dom The XML document to check.
+ * @return boolean Whether enclosures were found.
+ */
+ private function has_audio_enclosures( $dom ) {
+ $xpath = new DOMXPath( $dom );
+ $enclosures = $xpath->query( "//enclosure[starts-with(@type,'audio/')]" );
+ return ! $enclosures ? false : $enclosures->length > 0;
+ }
+
+}
+