// SPDX-FileCopyrightText: 2004-2023 Ryan Parman, Sam Sneddon, Ryan McCue
// SPDX-License-Identifier: BSD-3-Clause
* Manages all item-related data
* Used by {@see \SimplePie\SimplePie::get_item()} and {@see \SimplePie\SimplePie::get_items()}
* This class can be overloaded with {@see \SimplePie\SimplePie::set_item_class()}
class Item implements RegistryAware
* @var \SimplePie\SimplePie
* @var array<string, mixed>
* @var \SimplePie\Registry
private $sanitize = null;
* Create a new item object
* This is usually used by {@see \SimplePie\SimplePie::get_items} and
* {@see \SimplePie\SimplePie::get_item}. Avoid creating this manually.
* @param \SimplePie\SimplePie $feed Parent feed
* @param array<string, mixed> $data Raw data
public function __construct(\SimplePie\SimplePie $feed, array $data)
* Set the registry handler
* This is usually used by {@see \SimplePie\Registry::create}
* @param \SimplePie\Registry $registry
public function set_registry(\SimplePie\Registry $registry)
$this->registry = $registry;
* Get a string representation of the item
public function __toString()
return md5(serialize($this->data));
* Remove items that link back to this before destroying this object
public function __destruct()
* Get data for an item-level element
* This method allows you to get access to ANY element/attribute that is a
* sub-element of the item/entry tag.
* See {@see \SimplePie\SimplePie::get_feed_tags()} for a description of the return value
* @see http://simplepie.org/wiki/faq/supported_xml_namespaces
* @param string $namespace The URL of the XML namespace of the elements you're trying to access
* @param string $tag Tag name
* @return array<array<string, mixed>>|null
public function get_item_tags(string $namespace, string $tag)
if (isset($this->data['child'][$namespace][$tag])) {
return $this->data['child'][$namespace][$tag];
* Get base URL of the item itself.
* Returns `<xml:base>` or feed base URL.
* Similar to `Item::get_base()` but can safely be used during initialisation methods
* such as `Item::get_links()` (`Item::get_base()` and `Item::get_links()` call each-other)
* and is not affected by enclosures.
* @param array<string, mixed> $element
private function get_own_base(array $element = []): string
if (!empty($element['xml_base_explicit']) && isset($element['xml_base'])) {
return $element['xml_base'];
return $this->feed->get_base();
* Get the base URL value.
* Uses `<xml:base>`, or item link, or enclosure link, or feed base URL.
* @param array<string, mixed> $element
public function get_base(array $element = [])
if (!empty($element['xml_base_explicit']) && isset($element['xml_base'])) {
return $element['xml_base'];
$link = $this->get_permalink();
return $this->feed->get_base($element);
* @see \SimplePie\SimplePie::sanitize()
* @param string $data Data to sanitize
* @param int-mask-of<SimplePie::CONSTRUCT_*> $type
* @param string $base Base URL to resolve URLs against
* @return string Sanitized data
public function sanitize(string $data, int $type, string $base = '')
// This really returns string|false but changing encoding is uncommon and we are going to deprecate it, so let’s just lie to PHPStan in the interest of cleaner annotations.
return $this->feed->sanitize($data, $type, $base);
* Note: this may not work as you think for multifeeds!
* @link http://simplepie.org/faq/typical_multifeed_gotchas#missing_data_from_feed
* @return \SimplePie\SimplePie
public function get_feed()
* Get the unique identifier for the item
* This is usually used when writing code to check for new items in a feed.
* Uses `<atom:id>`, `<guid>`, `<dc:identifier>` or the `about` attribute
* for RDF. If none of these are supplied (or `$hash` is true), creates an
* MD5 hash based on the permalink, title and content.
* @param bool $hash Should we force using a hash instead of the supplied ID?
* @param string|false $fn User-supplied function to generate an hash
public function get_id(bool $hash = false, $fn = 'md5')
if ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'id')) {
return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT);
} elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'id')) {
return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT);
} elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_20, 'guid')) {
return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT);
} elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_DC_11, 'identifier')) {
return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT);
} elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_DC_10, 'identifier')) {
return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT);
} elseif (isset($this->data['attribs'][\SimplePie\SimplePie::NAMESPACE_RDF]['about'])) {
return $this->sanitize($this->data['attribs'][\SimplePie\SimplePie::NAMESPACE_RDF]['about'], \SimplePie\SimplePie::CONSTRUCT_TEXT);
} elseif (!is_callable($fn)) {
trigger_error('User-supplied function $fn must be callable', E_USER_WARNING);
$this->get_permalink().$this->get_title().$this->get_content()
* Get the title of the item
* Uses `<atom:title>`, `<title>` or `<dc:title>`
* @since Beta 2 (previously called `get_item_title` since 0.8)
public function get_title()
if (!isset($this->data['title'])) {
if ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'title')) {
$this->data['title'] = $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_10_construct_type', [$return[0]['attribs']]), $this->get_base($return[0]));
} elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'title')) {
$this->data['title'] = $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_03_construct_type', [$return[0]['attribs']]), $this->get_base($return[0]));
} elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_10, 'title')) {
$this->data['title'] = $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
} elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_090, 'title')) {
$this->data['title'] = $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
} elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_20, 'title')) {
$this->data['title'] = $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
} elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_DC_11, 'title')) {
$this->data['title'] = $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT);
} elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_DC_10, 'title')) {
$this->data['title'] = $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT);
$this->data['title'] = null;
return $this->data['title'];
* Get the content for the item
* Prefers summaries over full content , but will return full content if a
* summary does not exist.
* To prefer full content instead, use {@see get_content}
* Uses `<atom:summary>`, `<description>`, `<dc:description>` or
* @param bool $description_only Should we avoid falling back to the content?
public function get_description(bool $description_only = false)
if (($tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'summary')) &&
($return = $this->sanitize($tags[0]['data'], $this->registry->call(Misc::class, 'atom_10_construct_type', [$tags[0]['attribs']]), $this->get_base($tags[0])))) {
} elseif (($tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'summary')) &&
($return = $this->sanitize($tags[0]['data'], $this->registry->call(Misc::class, 'atom_03_construct_type', [$tags[0]['attribs']]), $this->get_base($tags[0])))) {
} elseif (($tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_10, 'description')) &&
($return = $this->sanitize($tags[0]['data'], \SimplePie\SimplePie::CONSTRUCT_MAYBE_HTML, $this->get_base($tags[0])))) {
} elseif (($tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_20, 'description')) &&
($return = $this->sanitize($tags[0]['data'], \SimplePie\SimplePie::CONSTRUCT_HTML, $this->get_base($tags[0])))) {
} elseif (($tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_DC_11, 'description')) &&
($return = $this->sanitize($tags[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT))) {
} elseif (($tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_DC_10, 'description')) &&
($return = $this->sanitize($tags[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT))) {
} elseif (($tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ITUNES, 'summary')) &&
($return = $this->sanitize($tags[0]['data'], \SimplePie\SimplePie::CONSTRUCT_HTML, $this->get_base($tags[0])))) {
} elseif (($tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ITUNES, 'subtitle')) &&
($return = $this->sanitize($tags[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT))) {
} elseif (($tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_090, 'description')) &&
($return = $this->sanitize($tags[0]['data'], \SimplePie\SimplePie::CONSTRUCT_HTML))) {
} elseif (!$description_only) {
return $this->get_content(true);
* Get the content for the item
* Prefers full content over summaries, but will return a summary if full
* content does not exist.
* To prefer summaries instead, use {@see get_description}
* Uses `<atom:content>` or `<content:encoded>` (RSS 1.0 Content Module)
* @param bool $content_only Should we avoid falling back to the description?
public function get_content(bool $content_only = false)
if (($tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'content')) &&
($return = $this->sanitize($tags[0]['data'], $this->registry->call(Misc::class, 'atom_10_content_construct_type', [$tags[0]['attribs']]), $this->get_base($tags[0])))) {
} elseif (($tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'content')) &&
($return = $this->sanitize($tags[0]['data'], $this->registry->call(Misc::class, 'atom_03_construct_type', [$tags[0]['attribs']]), $this->get_base($tags[0])))) {
} elseif (($tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_10_MODULES_CONTENT, 'encoded')) &&
($return = $this->sanitize($tags[0]['data'], \SimplePie\SimplePie::CONSTRUCT_HTML, $this->get_base($tags[0])))) {
} elseif (!$content_only) {
return $this->get_description(true);
* Get the media:thumbnail of the item
* Uses `<media:thumbnail>`
* @return array{url: string, height?: string, width?: string, time?: string}|null
public function get_thumbnail()
if (!isset($this->data['thumbnail'])) {
if ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'thumbnail')) {
$thumbnail = $return[0]['attribs'][''];
if (empty($thumbnail['url'])) {
$this->data['thumbnail'] = null;
$thumbnail['url'] = $this->sanitize($thumbnail['url'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_base($return[0]));
$this->data['thumbnail'] = $thumbnail;
$this->data['thumbnail'] = null;
return $this->data['thumbnail'];
* Get a category for the item
* @since Beta 3 (previously called `get_categories()` since Beta 2)
* @param int $key The category that you want to return. Remember that arrays begin with 0, not 1
* @return \SimplePie\Category|null
public function get_category(int $key = 0)
$categories = $this->get_categories();
if (isset($categories[$key])) {
return $categories[$key];
* Get all categories for the item
* Uses `<atom:category>`, `<category>` or `<dc:subject>`
* @return \SimplePie\Category[]|null List of {@see \SimplePie\Category} objects
public function get_categories()
foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, $type) as $category) {
if (isset($category['attribs']['']['term'])) {
$term = $this->sanitize($category['attribs']['']['term'], \SimplePie\SimplePie::CONSTRUCT_TEXT);
if (isset($category['attribs']['']['scheme'])) {
$scheme = $this->sanitize($category['attribs']['']['scheme'], \SimplePie\SimplePie::CONSTRUCT_TEXT);
if (isset($category['attribs']['']['label'])) {
$label = $this->sanitize($category['attribs']['']['label'], \SimplePie\SimplePie::CONSTRUCT_TEXT);
$categories[] = $this->registry->create(Category::class, [$term, $scheme, $label, $type]);
foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_20, $type) as $category) {
// This is really the label, but keep this as the term also for BC.
// Label will also work on retrieving because that falls back to term.
$term = $this->sanitize($category['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT);
if (isset($category['attribs']['']['domain'])) {
$scheme = $this->sanitize($category['attribs']['']['domain'], \SimplePie\SimplePie::CONSTRUCT_TEXT);
$categories[] = $this->registry->create(Category::class, [$term, $scheme, null, $type]);
foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_DC_11, $type) as $category) {
$categories[] = $this->registry->create(Category::class, [$this->sanitize($category['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT), null, null, $type]);
foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_DC_10, $type) as $category) {
$categories[] = $this->registry->create(Category::class, [$this->sanitize($category['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT), null, null, $type]);
if (!empty($categories)) {
return array_unique($categories);
* Get an author for the item
* @param int $key The author that you want to return. Remember that arrays begin with 0, not 1
* @return \SimplePie\Author|null
public function get_author(int $key = 0)
$authors = $this->get_authors();
if (isset($authors[$key])) {
* Get a contributor for the item
* @param int $key The contrbutor that you want to return. Remember that arrays begin with 0, not 1
* @return \SimplePie\Author|null
public function get_contributor(int $key = 0)
$contributors = $this->get_contributors();
if (isset($contributors[$key])) {
return $contributors[$key];
* Get all contributors for the item
* Uses `<atom:contributor>`
* @return \SimplePie\Author[]|null List of {@see \SimplePie\Author} objects
public function get_contributors()
foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'contributor') as $contributor) {
if (isset($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['name'][0]['data'])) {
$name = $this->sanitize($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['name'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT);
if (isset($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['uri'][0]['data'])) {
$uri = $contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['uri'][0];
$uri = $this->sanitize($uri['data'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_base($uri));
if (isset($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['email'][0]['data'])) {
$email = $this->sanitize($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['email'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT);