...channel elements... ... * * The channel's required elements (title, link, description) are always written. Items are * written exactly like custom XML items, in mapping order, so namespaces, attributes and * repeated elements work the same way. */ class Rss_Renderer extends Xml_Renderer { const ATOM_NS = 'http://www.w3.org/2005/Atom'; /** @var array Channel element => text. */ private $channel; /** @var string Address written as , or '' for none. */ private $self_url = ''; /** @var string Prefix bound to the Atom namespace, or '' when no self link is written. */ private $atom_prefix = ''; /** * @param array $channel Channel elements (see Rss_Profile::channel()). * @param array $namespaces Namespace declarations (prefix => URI). * @param bool $pretty Indent output. * @param string $self_url The feed's own address for . It must never * carry a credential; pass '' to write no self link. * @throws \InvalidArgumentException When a required channel element is missing. */ public function __construct( array $channel, array $namespaces = array(), bool $pretty = false, string $self_url = '' ) { foreach ( array( 'title', 'link', 'description' ) as $required ) { if ( ! isset( $channel[ $required ] ) || '' === trim( $channel[ $required ] ) ) { throw new \InvalidArgumentException( 'Missing RSS channel element.' ); } } // The self link needs the Atom namespace. Reuse a prefix the administrator already bound to it; // otherwise declare "atom". If "atom" is bound to something else, write no self link rather than // guess another prefix or rebind it. if ( Rss_Profile::is_http_url( $self_url ) ) { $existing = array_search( self::ATOM_NS, $namespaces, true ); if ( false !== $existing ) { $this->atom_prefix = (string) $existing; $this->self_url = $self_url; } elseif ( ! isset( $namespaces['atom'] ) ) { $namespaces['atom'] = self::ATOM_NS; $this->atom_prefix = 'atom'; $this->self_url = $self_url; } } parent::__construct( 'rss', 'item', $pretty, $namespaces ); $this->channel = $channel; } /** * {@inheritdoc} */ public function content_type(): string { return Rss_Profile::CONTENT_TYPE; } /** * Renders the document. * * @param array> $items Items, already checked by Rss_Profile::normalize_item(). * @return string */ public function render( array $items ): string { $writer = $this->open(); $this->start_root( $writer, 'rss', array( 'version' => '2.0' ) ); $writer->startElement( 'channel' ); foreach ( $this->channel as $name => $text ) { $this->write_leaf( $writer, $name, $text ); } if ( '' !== $this->self_url ) { $writer->startElement( $this->atom_prefix . ':link' ); $writer->writeAttribute( 'href', $this->self_url ); $writer->writeAttribute( 'rel', 'self' ); $writer->writeAttribute( 'type', 'application/rss+xml' ); $writer->endElement(); } // The newest item's date, so the document only changes when the content does. if ( $items ) { $first = isset( $items[0]['pubDate'] ) ? $items[0]['pubDate'] : null; if ( is_array( $first ) && isset( $first[ Mapping_Engine::VALUE ] ) ) { $first = $first[ Mapping_Engine::VALUE ]; } if ( is_string( $first ) && Rss_Profile::is_rss_date( $first ) ) { $this->write_leaf( $writer, 'lastBuildDate', $first ); } } foreach ( $items as $item ) { $this->write_item( $writer, 'item', $item ); } $writer->endElement(); $writer->endElement(); $writer->endDocument(); return $writer->outputMemory(); } }