...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();
}
}