forked from ZwiiCMS-Team/ZwiiCMS
416 lines
15 KiB
PHP
416 lines
15 KiB
PHP
<?php
|
|
namespace FeedWriter;
|
|
|
|
use \DateTime;
|
|
use \DateTimeInterface;
|
|
|
|
/*
|
|
* Copyright (C) 2008 Anis uddin Ahmad <anisniit@gmail.com>
|
|
* Copyright (C) 2010-2013, 2015-2016 Michael Bemmerl <mail@mx-server.de>
|
|
*
|
|
* This file is part of the "Universal Feed Writer" project.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/**
|
|
* Universal Feed Writer
|
|
*
|
|
* Item class - Used as feed element in Feed class
|
|
*
|
|
* @package UniversalFeedWriter
|
|
* @author Anis uddin Ahmad <anisniit@gmail.com>
|
|
* @link http://www.ajaxray.com/projects/rss
|
|
*/
|
|
class Item
|
|
{
|
|
/**
|
|
* Collection of feed item elements
|
|
*/
|
|
private $elements = array();
|
|
|
|
/**
|
|
* Contains the format of this feed.
|
|
*/
|
|
private $version;
|
|
|
|
/**
|
|
* Is used as a suffix when multiple elements have the same name.
|
|
**/
|
|
private $_cpt = 0;
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param string $version constant (RSS1/RSS2/ATOM) RSS2 is default.
|
|
*/
|
|
public function __construct($version = Feed::RSS2)
|
|
{
|
|
$this->version = $version;
|
|
}
|
|
|
|
/**
|
|
* Return an unique number
|
|
*
|
|
* @access private
|
|
* @return int
|
|
**/
|
|
private function cpt()
|
|
{
|
|
return $this->_cpt++;
|
|
}
|
|
|
|
/**
|
|
* Add an element to elements array
|
|
*
|
|
* @access public
|
|
* @param string $elementName The tag name of an element
|
|
* @param string $content The content of tag
|
|
* @param array $attributes Attributes (if any) in 'attrName' => 'attrValue' format
|
|
* @param boolean $overwrite Specifies if an already existing element is overwritten.
|
|
* @param boolean $allowMultiple Specifies if multiple elements of the same name are allowed.
|
|
* @return self
|
|
* @throws \InvalidArgumentException if the element name is not a string, empty or NULL.
|
|
*/
|
|
public function addElement($elementName, $content, array $attributes = null, $overwrite = FALSE, $allowMultiple = FALSE)
|
|
{
|
|
if (empty($elementName))
|
|
throw new \InvalidArgumentException('The element name may not be empty or NULL.');
|
|
if (!is_string($elementName))
|
|
throw new \InvalidArgumentException('The element name must be a string.');
|
|
|
|
$key = $elementName;
|
|
|
|
// return if element already exists & if overwriting is disabled
|
|
// & if multiple elements are not allowed.
|
|
if (isset($this->elements[$elementName]) && !$overwrite) {
|
|
if (!$allowMultiple)
|
|
return $this;
|
|
|
|
$key .= '-' . $this->cpt();
|
|
}
|
|
|
|
$this->elements[$key]['name'] = $elementName;
|
|
$this->elements[$key]['content'] = $content;
|
|
$this->elements[$key]['attributes'] = $attributes;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set multiple feed elements from an array.
|
|
* Elements which have attributes cannot be added by this method
|
|
*
|
|
* @access public
|
|
* @param array array of elements in 'tagName' => 'tagContent' format.
|
|
* @return self
|
|
*/
|
|
public function addElementArray(array $elementArray)
|
|
{
|
|
foreach ($elementArray as $elementName => $content) {
|
|
$this->addElement($elementName, $content);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Return the collection of elements in this feed item
|
|
*
|
|
* @access public
|
|
* @return array All elements of this item.
|
|
* @throws InvalidOperationException on ATOM feeds if either a content or link element is missing.
|
|
* @throws InvalidOperationException on RSS1 feeds if a title or link element is missing.
|
|
*/
|
|
public function getElements()
|
|
{
|
|
// ATOM feeds have some specific requirements...
|
|
if ($this->version == Feed::ATOM)
|
|
{
|
|
// Add an 'id' element, if it was not added by calling the setLink method.
|
|
// Use the value of the title element as key, since no link element was specified.
|
|
if (!array_key_exists('id', $this->elements))
|
|
$this->setId(Feed::uuid($this->elements['title']['content'], 'urn:uuid:'));
|
|
|
|
// Either a 'link' or 'content' element is needed.
|
|
if (!array_key_exists('content', $this->elements) && !array_key_exists('link', $this->elements))
|
|
throw new InvalidOperationException('ATOM feed entries need a link or a content element. Call the setLink or setContent method.');
|
|
}
|
|
// ...same with RSS1 feeds.
|
|
else if ($this->version == Feed::RSS1)
|
|
{
|
|
if (!array_key_exists('title', $this->elements))
|
|
throw new InvalidOperationException('RSS1 feed entries need a title element. Call the setTitle method.');
|
|
if (!array_key_exists('link', $this->elements))
|
|
throw new InvalidOperationException('RSS1 feed entries need a link element. Call the setLink method.');
|
|
}
|
|
|
|
return $this->elements;
|
|
}
|
|
|
|
/**
|
|
* Return the type of this feed item
|
|
*
|
|
* @access public
|
|
* @return string The feed type, as defined in Feed.php
|
|
*/
|
|
public function getVersion()
|
|
{
|
|
return $this->version;
|
|
}
|
|
|
|
// Wrapper functions ------------------------------------------------------
|
|
|
|
/**
|
|
* Set the 'description' element of feed item
|
|
*
|
|
* @access public
|
|
* @param string $description The content of the 'description' or 'summary' element
|
|
* @return self
|
|
*/
|
|
public function setDescription($description)
|
|
{
|
|
$tag = ($this->version == Feed::ATOM) ? 'summary' : 'description';
|
|
|
|
return $this->addElement($tag, $description);
|
|
}
|
|
|
|
/**
|
|
* Set the 'content' element of the feed item
|
|
* For ATOM feeds only
|
|
*
|
|
* @access public
|
|
* @param string $content Content for the item (i.e., the body of a blog post).
|
|
* @return self
|
|
* @throws InvalidOperationException if this method is called on non-ATOM feeds.
|
|
*/
|
|
public function setContent($content)
|
|
{
|
|
if ($this->version != Feed::ATOM)
|
|
throw new InvalidOperationException('The content element is supported in ATOM feeds only.');
|
|
|
|
return $this->addElement('content', $content, array('type' => 'html'));
|
|
}
|
|
|
|
/**
|
|
* Set the 'title' element of feed item
|
|
*
|
|
* @access public
|
|
* @param string $title The content of 'title' element
|
|
* @return self
|
|
*/
|
|
public function setTitle($title)
|
|
{
|
|
return $this->addElement('title', $title);
|
|
}
|
|
|
|
/**
|
|
* Set the 'date' element of the feed item.
|
|
*
|
|
* The value of the date parameter can be either a class implementing
|
|
* DateTimeInterface, an integer containing a UNIX timestamp or a string
|
|
* which is parseable by PHP's 'strtotime' function.
|
|
*
|
|
* @access public
|
|
* @param DateTimeInterface|int|string $date Date which should be used.
|
|
* @return self
|
|
* @throws \InvalidArgumentException if the given date was not parseable.
|
|
*/
|
|
public function setDate($date)
|
|
{
|
|
if (!is_numeric($date)) {
|
|
if ($date instanceof DateTimeInterface || $date instanceof DateTime)
|
|
$date = $date->getTimestamp();
|
|
else {
|
|
$date = strtotime($date);
|
|
|
|
if ($date === FALSE)
|
|
throw new \InvalidArgumentException('The given date string was not parseable.');
|
|
}
|
|
} elseif ($date < 0)
|
|
throw new \InvalidArgumentException('The given date is not an UNIX timestamp.');
|
|
|
|
if ($this->version == Feed::ATOM) {
|
|
$tag = 'updated';
|
|
$value = date(\DATE_ATOM, $date);
|
|
} elseif ($this->version == Feed::RSS2) {
|
|
$tag = 'pubDate';
|
|
$value = date(\DATE_RSS, $date);
|
|
} else {
|
|
$tag = 'dc:date';
|
|
$value = date("Y-m-d", $date);
|
|
}
|
|
|
|
return $this->addElement($tag, $value);
|
|
}
|
|
|
|
/**
|
|
* Set the 'link' element of feed item
|
|
*
|
|
* @access public
|
|
* @param string $link The content of 'link' element
|
|
* @return self
|
|
*/
|
|
public function setLink($link)
|
|
{
|
|
if ($this->version == Feed::RSS2 || $this->version == Feed::RSS1) {
|
|
$this->addElement('link', $link);
|
|
} else {
|
|
$this->addElement('link','',array('href'=>$link));
|
|
$this->setId(Feed::uuid($link,'urn:uuid:'));
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Attach a external media to the feed item.
|
|
* Not supported in RSS 1.0 feeds.
|
|
*
|
|
* See RFC 6838 for syntactical correct MIME types.
|
|
*
|
|
* Note that you should avoid the use of more than one enclosure in one item,
|
|
* since some RSS aggregators don't support it.
|
|
*
|
|
* @access public
|
|
* @param string $url The URL of the media.
|
|
* @param integer $length The length of the media.
|
|
* @param string $type The MIME type attribute of the media.
|
|
* @param boolean $multiple Specifies if multiple enclosures are allowed
|
|
* @return self
|
|
* @link https://tools.ietf.org/html/rfc6838
|
|
* @link http://www.iana.org/assignments/media-types/media-types.xhtml
|
|
* @throws \InvalidArgumentException if the length or type parameter is invalid.
|
|
* @throws InvalidOperationException if this method is called on RSS1 feeds.
|
|
*/
|
|
public function addEnclosure($url, $length, $type, $multiple = TRUE)
|
|
{
|
|
if ($this->version == Feed::RSS1)
|
|
throw new InvalidOperationException('Media attachment is not supported in RSS1 feeds.');
|
|
|
|
// the length parameter should be set to 0 if it can't be determined
|
|
// see http://www.rssboard.org/rss-profile#element-channel-item-enclosure
|
|
if (!is_numeric($length) || $length < 0)
|
|
throw new \InvalidArgumentException('The length parameter must be an integer and greater or equals to zero.');
|
|
|
|
// Regex used from RFC 4287, page 41
|
|
if (!is_string($type) || preg_match('/.+\/.+/', $type) != 1)
|
|
throw new \InvalidArgumentException('type parameter must be a string and a MIME type.');
|
|
|
|
$attributes = array('length' => $length, 'type' => $type);
|
|
|
|
if ($this->version == Feed::RSS2) {
|
|
$attributes['url'] = $url;
|
|
$this->addElement('enclosure', '', $attributes, FALSE, $multiple);
|
|
} else {
|
|
$attributes['href'] = $url;
|
|
$attributes['rel'] = 'enclosure';
|
|
$this->addElement('atom:link', '', $attributes, FALSE, $multiple);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set the 'author' element of feed item.
|
|
* Not supported in RSS 1.0 feeds.
|
|
*
|
|
* @access public
|
|
* @param string $author The author of this item
|
|
* @param string|null $email Optional email address of the author
|
|
* @param string|null $uri Optional URI related to the author
|
|
* @return self
|
|
* @throws \InvalidArgumentException if the provided email address is syntactically incorrect.
|
|
* @throws InvalidOperationException if this method is called on RSS1 feeds.
|
|
*/
|
|
public function setAuthor($author, $email = null, $uri = null)
|
|
{
|
|
if ($this->version == Feed::RSS1)
|
|
throw new InvalidOperationException('The author element is not supported in RSS1 feeds.');
|
|
|
|
// Regex from RFC 4287 page 41
|
|
if ($email != null && preg_match('/.+@.+/', $email) != 1)
|
|
throw new \InvalidArgumentException('The email address is syntactically incorrect.');
|
|
|
|
if ($this->version == Feed::RSS2)
|
|
{
|
|
if ($email != null)
|
|
$author = $email . ' (' . $author . ')';
|
|
|
|
$this->addElement('author', $author);
|
|
}
|
|
else
|
|
{
|
|
$elements = array('name' => $author);
|
|
|
|
if ($email != null)
|
|
$elements['email'] = $email;
|
|
|
|
if ($uri != null)
|
|
$elements['uri'] = $uri;
|
|
|
|
$this->addElement('author', $elements);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set the unique identifier of the feed item
|
|
*
|
|
* On ATOM feeds, the identifier must begin with an valid URI scheme.
|
|
*
|
|
* @access public
|
|
* @param string $id The unique identifier of this item
|
|
* @param boolean $permaLink The value of the 'isPermaLink' attribute in RSS 2 feeds.
|
|
* @return self
|
|
* @throws \InvalidArgumentException if the permaLink parameter is not boolean.
|
|
* @throws InvalidOperationException if this method is called on RSS1 feeds.
|
|
*/
|
|
public function setId($id, $permaLink = false)
|
|
{
|
|
if ($this->version == Feed::RSS2) {
|
|
if (!is_bool($permaLink))
|
|
throw new \InvalidArgumentException('The permaLink parameter must be boolean.');
|
|
|
|
$permaLink = $permaLink ? 'true' : 'false';
|
|
|
|
$this->addElement('guid', $id, array('isPermaLink' => $permaLink));
|
|
} elseif ($this->version == Feed::ATOM) {
|
|
// Check if the given ID is an valid URI scheme (see RFC 4287 4.2.6)
|
|
// The list of valid schemes was generated from http://www.iana.org/assignments/uri-schemes
|
|
// by using only permanent or historical schemes.
|
|
$validSchemes = array('aaa', 'aaas', 'about', 'acap', 'acct', 'bb', 'cap', 'cid', 'coap', 'coap+tcp', 'coap+ws', 'coaps', 'coaps+tcp', 'coaps+ws', 'crid', 'data', 'dav', 'dict', 'dns', 'drop', 'dtn', 'example', 'fax', 'file', 'filesystem', 'ftp', 'geo', 'go', 'gopher', 'grd', 'h323', 'http', 'https', 'iax', 'icap', 'im', 'imap', 'info', 'ipn', 'ipp', 'ipps', 'iris', 'iris.beep', 'iris.lwz', 'iris.xpc', 'iris.xpcs', 'jabber', 'ldap', 'leaptofrogans', 'mailserver', 'mailto', 'mid', 'modem', 'msrp', 'msrps', 'mt', 'mtqp', 'mupdate', 'news', 'nfs', 'ni', 'nih', 'nntp', 'opaquelocktoken', 'p1', 'pack', 'pkcs11', 'pop', 'pres', 'prospero', 'reload', 'rtsp', 'rtsps', 'rtspu', 'service', 'session', 'shttp (OBSOLETE)', 'sieve', 'sip', 'sips', 'sms', 'snews', 'snmp', 'soap.beep', 'soap.beeps', 'stun', 'stuns', 'tag', 'tel', 'telnet', 'tftp', 'thismessage', 'tip', 'tn3270', 'turn', 'turns', 'tv', 'upt', 'urn', 'vemmi', 'videotex', 'vnc', 'wais', 'wpid', 'ws', 'wss', 'xcon', 'xcon-userid', 'xmlrpc.beep', 'xmlrpc.beeps', 'xmpp', 'z39.50', 'z39.50r', 'z39.50s');
|
|
$found = FALSE;
|
|
$checkId = strtolower($id);
|
|
|
|
foreach($validSchemes as $scheme)
|
|
if (strrpos($checkId, $scheme . ':', -strlen($checkId)) !== FALSE)
|
|
{
|
|
$found = TRUE;
|
|
break;
|
|
}
|
|
|
|
if (!$found)
|
|
throw new \InvalidArgumentException("The ID must begin with an IANA-registered URI scheme.");
|
|
|
|
$this->addElement('id', $id, NULL, TRUE);
|
|
} else
|
|
throw new InvalidOperationException('A unique ID is not supported in RSS1 feeds.');
|
|
|
|
return $this;
|
|
}
|
|
|
|
} // end of class Item
|