Chris@0: [ Chris@0: 'DublinCore\Feed', Chris@0: 'Atom\Feed' Chris@0: ], Chris@0: 'entry' => [ Chris@0: 'Content\Entry', Chris@0: 'DublinCore\Entry', Chris@0: 'Atom\Entry' Chris@0: ], Chris@0: 'core' => [ Chris@0: 'DublinCore\Feed', Chris@0: 'Atom\Feed', Chris@0: 'Content\Entry', Chris@0: 'DublinCore\Entry', Chris@0: 'Atom\Entry' Chris@0: ] Chris@0: ]; Chris@0: Chris@0: /** Chris@0: * Get the Feed cache Chris@0: * Chris@0: * @return CacheStorage Chris@0: */ Chris@0: public static function getCache() Chris@0: { Chris@0: return static::$cache; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Set the feed cache Chris@0: * Chris@0: * @param CacheStorage $cache Chris@0: * @return void Chris@0: */ Chris@0: public static function setCache(CacheStorage $cache) Chris@0: { Chris@0: static::$cache = $cache; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Set the HTTP client instance Chris@0: * Chris@0: * Sets the HTTP client object to use for retrieving the feeds. Chris@0: * Chris@0: * @param ZendHttp\Client | Http\ClientInterface $httpClient Chris@0: * @return void Chris@0: */ Chris@0: public static function setHttpClient($httpClient) Chris@0: { Chris@0: if ($httpClient instanceof ZendHttp\Client) { Chris@0: $httpClient = new Http\ZendHttpClientDecorator($httpClient); Chris@0: } Chris@0: Chris@0: if (! $httpClient instanceof Http\ClientInterface) { Chris@0: throw new InvalidHttpClientException(); Chris@0: } Chris@0: static::$httpClient = $httpClient; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the HTTP client object. If none is set, a new ZendHttp\Client will be used. Chris@0: * Chris@0: * @return Http\ClientInterface Chris@0: */ Chris@0: public static function getHttpClient() Chris@0: { Chris@0: if (! static::$httpClient) { Chris@0: static::$httpClient = new Http\ZendHttpClientDecorator(new ZendHttp\Client()); Chris@0: } Chris@0: Chris@0: return static::$httpClient; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Toggle using POST instead of PUT and DELETE HTTP methods Chris@0: * Chris@0: * Some feed implementations do not accept PUT and DELETE HTTP Chris@0: * methods, or they can't be used because of proxies or other Chris@0: * measures. This allows turning on using POST where PUT and Chris@0: * DELETE would normally be used; in addition, an Chris@0: * X-Method-Override header will be sent with a value of PUT or Chris@0: * DELETE as appropriate. Chris@0: * Chris@0: * @param bool $override Whether to override PUT and DELETE. Chris@0: * @return void Chris@0: */ Chris@0: public static function setHttpMethodOverride($override = true) Chris@0: { Chris@0: static::$httpMethodOverride = $override; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get the HTTP override state Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: public static function getHttpMethodOverride() Chris@0: { Chris@0: return static::$httpMethodOverride; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Set the flag indicating whether or not to use HTTP conditional GET Chris@0: * Chris@0: * @param bool $bool Chris@0: * @return void Chris@0: */ Chris@0: public static function useHttpConditionalGet($bool = true) Chris@0: { Chris@0: static::$httpConditionalGet = $bool; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Import a feed by providing a URI Chris@0: * Chris@0: * @param string $uri The URI to the feed Chris@0: * @param string $etag OPTIONAL Last received ETag for this resource Chris@0: * @param string $lastModified OPTIONAL Last-Modified value for this resource Chris@0: * @return Feed\FeedInterface Chris@0: * @throws Exception\RuntimeException Chris@0: */ Chris@0: public static function import($uri, $etag = null, $lastModified = null) Chris@0: { Chris@0: $cache = self::getCache(); Chris@0: $client = self::getHttpClient(); Chris@0: $cacheId = 'Zend_Feed_Reader_' . md5($uri); Chris@0: Chris@0: if (static::$httpConditionalGet && $cache) { Chris@0: $headers = []; Chris@0: $data = $cache->getItem($cacheId); Chris@0: if ($data && $client instanceof Http\HeaderAwareClientInterface) { Chris@0: // Only check for ETag and last modified values in the cache Chris@0: // if we have a client capable of emitting headers in the first place. Chris@0: if ($etag === null) { Chris@0: $etag = $cache->getItem($cacheId . '_etag'); Chris@0: } Chris@0: if ($lastModified === null) { Chris@0: $lastModified = $cache->getItem($cacheId . '_lastmodified'); Chris@0: } Chris@0: if ($etag) { Chris@0: $headers['If-None-Match'] = [$etag]; Chris@0: } Chris@0: if ($lastModified) { Chris@0: $headers['If-Modified-Since'] = [$lastModified]; Chris@0: } Chris@0: } Chris@0: $response = $client->get($uri, $headers); Chris@0: if ($response->getStatusCode() !== 200 && $response->getStatusCode() !== 304) { Chris@12: throw new Exception\RuntimeException( Chris@12: 'Feed failed to load, got response code ' . $response->getStatusCode() Chris@12: ); Chris@0: } Chris@0: if ($response->getStatusCode() == 304) { Chris@0: $responseXml = $data; Chris@0: } else { Chris@0: $responseXml = $response->getBody(); Chris@0: $cache->setItem($cacheId, $responseXml); Chris@0: Chris@0: if ($response instanceof Http\HeaderAwareResponseInterface) { Chris@0: if ($response->getHeaderLine('ETag', false)) { Chris@0: $cache->setItem($cacheId . '_etag', $response->getHeaderLine('ETag')); Chris@0: } Chris@0: if ($response->getHeaderLine('Last-Modified', false)) { Chris@0: $cache->setItem($cacheId . '_lastmodified', $response->getHeaderLine('Last-Modified')); Chris@0: } Chris@0: } Chris@0: } Chris@0: return static::importString($responseXml); Chris@0: } elseif ($cache) { Chris@0: $data = $cache->getItem($cacheId); Chris@0: if ($data) { Chris@0: return static::importString($data); Chris@0: } Chris@0: $response = $client->get($uri); Chris@0: if ((int) $response->getStatusCode() !== 200) { Chris@12: throw new Exception\RuntimeException( Chris@12: 'Feed failed to load, got response code ' . $response->getStatusCode() Chris@12: ); Chris@0: } Chris@0: $responseXml = $response->getBody(); Chris@0: $cache->setItem($cacheId, $responseXml); Chris@0: return static::importString($responseXml); Chris@0: } else { Chris@0: $response = $client->get($uri); Chris@0: if ((int) $response->getStatusCode() !== 200) { Chris@12: throw new Exception\RuntimeException( Chris@12: 'Feed failed to load, got response code ' . $response->getStatusCode() Chris@12: ); Chris@0: } Chris@0: $reader = static::importString($response->getBody()); Chris@0: $reader->setOriginalSourceUri($uri); Chris@0: return $reader; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Import a feed from a remote URI Chris@0: * Chris@0: * Performs similarly to import(), except it uses the HTTP client passed to Chris@0: * the method, and does not take into account cached data. Chris@0: * Chris@0: * Primary purpose is to make it possible to use the Reader with alternate Chris@0: * HTTP client implementations. Chris@0: * Chris@0: * @param string $uri Chris@0: * @param Http\ClientInterface $client Chris@17: * @return Feed\FeedInterface Chris@0: * @throws Exception\RuntimeException if response is not an Http\ResponseInterface Chris@0: */ Chris@0: public static function importRemoteFeed($uri, Http\ClientInterface $client) Chris@0: { Chris@0: $response = $client->get($uri); Chris@0: if (! $response instanceof Http\ResponseInterface) { Chris@0: throw new Exception\RuntimeException(sprintf( Chris@0: 'Did not receive a %s\Http\ResponseInterface from the provided HTTP client; received "%s"', Chris@0: __NAMESPACE__, Chris@0: (is_object($response) ? get_class($response) : gettype($response)) Chris@0: )); Chris@0: } Chris@0: Chris@0: if ((int) $response->getStatusCode() !== 200) { Chris@12: throw new Exception\RuntimeException( Chris@12: 'Feed failed to load, got response code ' . $response->getStatusCode() Chris@12: ); Chris@0: } Chris@0: $reader = static::importString($response->getBody()); Chris@0: $reader->setOriginalSourceUri($uri); Chris@0: return $reader; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Import a feed from a string Chris@0: * Chris@0: * @param string $string Chris@0: * @return Feed\FeedInterface Chris@0: * @throws Exception\InvalidArgumentException Chris@0: * @throws Exception\RuntimeException Chris@0: */ Chris@0: public static function importString($string) Chris@0: { Chris@0: $trimmed = trim($string); Chris@12: if (! is_string($string) || empty($trimmed)) { Chris@0: throw new Exception\InvalidArgumentException('Only non empty strings are allowed as input'); Chris@0: } Chris@0: Chris@0: $libxmlErrflag = libxml_use_internal_errors(true); Chris@0: $oldValue = libxml_disable_entity_loader(true); Chris@0: $dom = new DOMDocument; Chris@0: $status = $dom->loadXML(trim($string)); Chris@0: foreach ($dom->childNodes as $child) { Chris@0: if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) { Chris@0: throw new Exception\InvalidArgumentException( Chris@0: 'Invalid XML: Detected use of illegal DOCTYPE' Chris@0: ); Chris@0: } Chris@0: } Chris@0: libxml_disable_entity_loader($oldValue); Chris@0: libxml_use_internal_errors($libxmlErrflag); Chris@0: Chris@12: if (! $status) { Chris@0: // Build error message Chris@0: $error = libxml_get_last_error(); Chris@0: if ($error && $error->message) { Chris@0: $error->message = trim($error->message); Chris@0: $errormsg = "DOMDocument cannot parse XML: {$error->message}"; Chris@0: } else { Chris@0: $errormsg = "DOMDocument cannot parse XML: Please check the XML document's validity"; Chris@0: } Chris@0: throw new Exception\RuntimeException($errormsg); Chris@0: } Chris@0: Chris@0: $type = static::detectType($dom); Chris@0: Chris@0: static::registerCoreExtensions(); Chris@0: Chris@17: if (0 === strpos($type, 'rss')) { Chris@0: $reader = new Feed\Rss($dom, $type); Chris@17: } elseif (8 === strpos($type, 'entry')) { Chris@0: $reader = new Entry\Atom($dom->documentElement, 0, self::TYPE_ATOM_10); Chris@17: } elseif (0 === strpos($type, 'atom')) { Chris@0: $reader = new Feed\Atom($dom, $type); Chris@0: } else { Chris@0: throw new Exception\RuntimeException('The URI used does not point to a ' Chris@0: . 'valid Atom, RSS or RDF feed that Zend\Feed\Reader can parse.'); Chris@0: } Chris@0: return $reader; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Imports a feed from a file located at $filename. Chris@0: * Chris@0: * @param string $filename Chris@0: * @throws Exception\RuntimeException Chris@0: * @return Feed\FeedInterface Chris@0: */ Chris@0: public static function importFile($filename) Chris@0: { Chris@0: ErrorHandler::start(); Chris@0: $feed = file_get_contents($filename); Chris@0: $err = ErrorHandler::stop(); Chris@0: if ($feed === false) { Chris@0: throw new Exception\RuntimeException("File '{$filename}' could not be loaded", 0, $err); Chris@0: } Chris@0: return static::importString($feed); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Find feed links Chris@0: * Chris@0: * @param $uri Chris@0: * @return FeedSet Chris@0: * @throws Exception\RuntimeException Chris@0: */ Chris@0: public static function findFeedLinks($uri) Chris@0: { Chris@0: $client = static::getHttpClient(); Chris@0: $response = $client->get($uri); Chris@0: if ($response->getStatusCode() !== 200) { Chris@12: throw new Exception\RuntimeException( Chris@12: "Failed to access $uri, got response code " . $response->getStatusCode() Chris@12: ); Chris@0: } Chris@0: $responseHtml = $response->getBody(); Chris@0: $libxmlErrflag = libxml_use_internal_errors(true); Chris@0: $oldValue = libxml_disable_entity_loader(true); Chris@0: $dom = new DOMDocument; Chris@0: $status = $dom->loadHTML(trim($responseHtml)); Chris@0: libxml_disable_entity_loader($oldValue); Chris@0: libxml_use_internal_errors($libxmlErrflag); Chris@12: if (! $status) { Chris@0: // Build error message Chris@0: $error = libxml_get_last_error(); Chris@0: if ($error && $error->message) { Chris@0: $error->message = trim($error->message); Chris@0: $errormsg = "DOMDocument cannot parse HTML: {$error->message}"; Chris@0: } else { Chris@0: $errormsg = "DOMDocument cannot parse HTML: Please check the XML document's validity"; Chris@0: } Chris@0: throw new Exception\RuntimeException($errormsg); Chris@0: } Chris@0: $feedSet = new FeedSet; Chris@0: $links = $dom->getElementsByTagName('link'); Chris@0: $feedSet->addLinks($links, $uri); Chris@0: return $feedSet; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Detect the feed type of the provided feed Chris@0: * Chris@0: * @param Feed\AbstractFeed|DOMDocument|string $feed Chris@0: * @param bool $specOnly Chris@0: * @return string Chris@0: * @throws Exception\InvalidArgumentException Chris@0: * @throws Exception\RuntimeException Chris@0: */ Chris@0: public static function detectType($feed, $specOnly = false) Chris@0: { Chris@0: if ($feed instanceof Feed\AbstractFeed) { Chris@0: $dom = $feed->getDomDocument(); Chris@0: } elseif ($feed instanceof DOMDocument) { Chris@0: $dom = $feed; Chris@12: } elseif (is_string($feed) && ! empty($feed)) { Chris@12: ErrorHandler::start(E_NOTICE | E_WARNING); Chris@0: ini_set('track_errors', 1); Chris@0: $oldValue = libxml_disable_entity_loader(true); Chris@0: $dom = new DOMDocument; Chris@0: $status = $dom->loadXML($feed); Chris@0: foreach ($dom->childNodes as $child) { Chris@0: if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) { Chris@0: throw new Exception\InvalidArgumentException( Chris@0: 'Invalid XML: Detected use of illegal DOCTYPE' Chris@0: ); Chris@0: } Chris@0: } Chris@0: libxml_disable_entity_loader($oldValue); Chris@0: ini_restore('track_errors'); Chris@0: ErrorHandler::stop(); Chris@12: if (! $status) { Chris@12: if (! isset($phpErrormsg)) { Chris@0: if (function_exists('xdebug_is_enabled')) { Chris@0: $phpErrormsg = '(error message not available, when XDebug is running)'; Chris@0: } else { Chris@0: $phpErrormsg = '(error message not available)'; Chris@0: } Chris@0: } Chris@0: throw new Exception\RuntimeException("DOMDocument cannot parse XML: $phpErrormsg"); Chris@0: } Chris@0: } else { Chris@0: throw new Exception\InvalidArgumentException('Invalid object/scalar provided: must' Chris@0: . ' be of type Zend\Feed\Reader\Feed, DomDocument or string'); Chris@0: } Chris@0: $xpath = new DOMXPath($dom); Chris@0: Chris@0: if ($xpath->query('/rss')->length) { Chris@0: $type = self::TYPE_RSS_ANY; Chris@0: $version = $xpath->evaluate('string(/rss/@version)'); Chris@0: Chris@0: if (strlen($version) > 0) { Chris@0: switch ($version) { Chris@0: case '2.0': Chris@0: $type = self::TYPE_RSS_20; Chris@0: break; Chris@0: Chris@0: case '0.94': Chris@0: $type = self::TYPE_RSS_094; Chris@0: break; Chris@0: Chris@0: case '0.93': Chris@0: $type = self::TYPE_RSS_093; Chris@0: break; Chris@0: Chris@0: case '0.92': Chris@0: $type = self::TYPE_RSS_092; Chris@0: break; Chris@0: Chris@0: case '0.91': Chris@0: $type = self::TYPE_RSS_091; Chris@0: break; Chris@0: } Chris@0: } Chris@0: Chris@0: return $type; Chris@0: } Chris@0: Chris@0: $xpath->registerNamespace('rdf', self::NAMESPACE_RDF); Chris@0: Chris@0: if ($xpath->query('/rdf:RDF')->length) { Chris@0: $xpath->registerNamespace('rss', self::NAMESPACE_RSS_10); Chris@0: Chris@0: if ($xpath->query('/rdf:RDF/rss:channel')->length Chris@0: || $xpath->query('/rdf:RDF/rss:image')->length Chris@0: || $xpath->query('/rdf:RDF/rss:item')->length Chris@0: || $xpath->query('/rdf:RDF/rss:textinput')->length Chris@0: ) { Chris@0: return self::TYPE_RSS_10; Chris@0: } Chris@0: Chris@0: $xpath->registerNamespace('rss', self::NAMESPACE_RSS_090); Chris@0: Chris@0: if ($xpath->query('/rdf:RDF/rss:channel')->length Chris@0: || $xpath->query('/rdf:RDF/rss:image')->length Chris@0: || $xpath->query('/rdf:RDF/rss:item')->length Chris@0: || $xpath->query('/rdf:RDF/rss:textinput')->length Chris@0: ) { Chris@0: return self::TYPE_RSS_090; Chris@0: } Chris@0: } Chris@0: Chris@0: $xpath->registerNamespace('atom', self::NAMESPACE_ATOM_10); Chris@0: Chris@0: if ($xpath->query('//atom:feed')->length) { Chris@0: return self::TYPE_ATOM_10; Chris@0: } Chris@0: Chris@0: if ($xpath->query('//atom:entry')->length) { Chris@0: if ($specOnly == true) { Chris@0: return self::TYPE_ATOM_10; Chris@0: } else { Chris@0: return self::TYPE_ATOM_10_ENTRY; Chris@0: } Chris@0: } Chris@0: Chris@0: $xpath->registerNamespace('atom', self::NAMESPACE_ATOM_03); Chris@0: Chris@0: if ($xpath->query('//atom:feed')->length) { Chris@0: return self::TYPE_ATOM_03; Chris@0: } Chris@0: Chris@0: return self::TYPE_ANY; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Set plugin manager for use with Extensions Chris@0: * Chris@0: * @param ExtensionManagerInterface $extensionManager Chris@0: */ Chris@0: public static function setExtensionManager(ExtensionManagerInterface $extensionManager) Chris@0: { Chris@0: static::$extensionManager = $extensionManager; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get plugin manager for use with Extensions Chris@0: * Chris@0: * @return ExtensionManagerInterface Chris@0: */ Chris@0: public static function getExtensionManager() Chris@0: { Chris@12: if (! isset(static::$extensionManager)) { Chris@0: static::setExtensionManager(new StandaloneExtensionManager()); Chris@0: } Chris@0: return static::$extensionManager; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Register an Extension by name Chris@0: * Chris@0: * @param string $name Chris@0: * @return void Chris@0: * @throws Exception\RuntimeException if unable to resolve Extension class Chris@0: */ Chris@0: public static function registerExtension($name) Chris@0: { Chris@16: if (! static::hasExtension($name)) { Chris@16: throw new Exception\RuntimeException(sprintf( Chris@16: 'Could not load extension "%s" using Plugin Loader.' Chris@16: . ' Check prefix paths are configured and extension exists.', Chris@16: $name Chris@16: )); Chris@0: } Chris@0: Chris@16: // Return early if already registered. Chris@16: if (static::isRegistered($name)) { Chris@16: return; Chris@0: } Chris@16: Chris@16: $manager = static::getExtensionManager(); Chris@16: Chris@16: $feedName = $name . '\Feed'; Chris@0: if ($manager->has($feedName)) { Chris@0: static::$extensions['feed'][] = $feedName; Chris@0: } Chris@16: Chris@16: $entryName = $name . '\Entry'; Chris@0: if ($manager->has($entryName)) { Chris@0: static::$extensions['entry'][] = $entryName; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Is a given named Extension registered? Chris@0: * Chris@0: * @param string $extensionName Chris@0: * @return bool Chris@0: */ Chris@0: public static function isRegistered($extensionName) Chris@0: { Chris@0: $feedName = $extensionName . '\Feed'; Chris@0: $entryName = $extensionName . '\Entry'; Chris@0: if (in_array($feedName, static::$extensions['feed']) Chris@0: || in_array($entryName, static::$extensions['entry']) Chris@0: ) { Chris@0: return true; Chris@0: } Chris@0: return false; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get a list of extensions Chris@0: * Chris@0: * @return array Chris@0: */ Chris@0: public static function getExtensions() Chris@0: { Chris@0: return static::$extensions; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Reset class state to defaults Chris@0: * Chris@0: * @return void Chris@0: */ Chris@0: public static function reset() Chris@0: { Chris@0: static::$cache = null; Chris@0: static::$httpClient = null; Chris@0: static::$httpMethodOverride = false; Chris@0: static::$httpConditionalGet = false; Chris@0: static::$extensionManager = null; Chris@0: static::$extensions = [ Chris@0: 'feed' => [ Chris@0: 'DublinCore\Feed', Chris@0: 'Atom\Feed' Chris@0: ], Chris@0: 'entry' => [ Chris@0: 'Content\Entry', Chris@0: 'DublinCore\Entry', Chris@0: 'Atom\Entry' Chris@0: ], Chris@0: 'core' => [ Chris@0: 'DublinCore\Feed', Chris@0: 'Atom\Feed', Chris@0: 'Content\Entry', Chris@0: 'DublinCore\Entry', Chris@0: 'Atom\Entry' Chris@0: ] Chris@0: ]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Register core (default) extensions Chris@0: * Chris@0: * @return void Chris@0: */ Chris@0: protected static function registerCoreExtensions() Chris@0: { Chris@0: static::registerExtension('DublinCore'); Chris@0: static::registerExtension('Content'); Chris@0: static::registerExtension('Atom'); Chris@0: static::registerExtension('Slash'); Chris@0: static::registerExtension('WellFormedWeb'); Chris@0: static::registerExtension('Thread'); Chris@0: static::registerExtension('Podcast'); Chris@16: Chris@16: // Added in 2.10.0; check for it conditionally Chris@16: static::hasExtension('GooglePlayPodcast') Chris@16: ? static::registerExtension('GooglePlayPodcast') Chris@16: : trigger_error( Chris@16: sprintf( Chris@16: 'Please update your %1$s\ExtensionManagerInterface implementation to add entries for' Chris@16: . ' %1$s\Extension\GooglePlayPodcast\Entry and %1$s\Extension\GooglePlayPodcast\Feed.', Chris@16: __NAMESPACE__ Chris@16: ), Chris@16: \E_USER_NOTICE Chris@16: ); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Utility method to apply array_unique operation to a multidimensional Chris@0: * array. Chris@0: * Chris@0: * @param array Chris@0: * @return array Chris@0: */ Chris@0: public static function arrayUnique(array $array) Chris@0: { Chris@0: foreach ($array as &$value) { Chris@0: $value = serialize($value); Chris@0: } Chris@0: $array = array_unique($array); Chris@0: foreach ($array as &$value) { Chris@0: $value = unserialize($value); Chris@0: } Chris@0: return $array; Chris@0: } Chris@16: Chris@16: /** Chris@16: * Does the extension manager have the named extension? Chris@16: * Chris@16: * This method exists to allow us to test if an extension is present in the Chris@16: * extension manager. It may be used by registerExtension() to determine if Chris@16: * the extension has items present in the manager, or by Chris@16: * registerCoreExtension() to determine if the core extension has entries Chris@16: * in the extension manager. In the latter case, this can be useful when Chris@16: * adding new extensions in a minor release, as custom extension manager Chris@16: * implementations may not yet have an entry for the extension, which would Chris@16: * then otherwise cause registerExtension() to fail. Chris@16: * Chris@16: * @param string $name Chris@16: * @return bool Chris@16: */ Chris@16: protected static function hasExtension($name) Chris@16: { Chris@16: $feedName = $name . '\Feed'; Chris@16: $entryName = $name . '\Entry'; Chris@16: $manager = static::getExtensionManager(); Chris@16: Chris@16: return $manager->has($feedName) || $manager->has($entryName); Chris@16: } Chris@0: }