This class has two public setter methods which take arrays as parameters. The class parses the arrays for all required and all optional RSS 2.0 elements. Additional namespaces are not supported in this version.
The class attempts to translate input characters into html entities in most cases, but it does tolerate (X)HTML markup in 'description' tags, and wraps the content of description tags in a CDATA section. It also enforces integer values where integer values are expected, and enforces required attributes and sub-elements in optional elements.
The output XML can be written to file, or displayed in a browser, and it is also possible to "force" a browser to download the XML file rather than to display it.
@author Richard Lucas <webmaster@applebiter.com>
@category RSS
@version 0.1
@copyright Copyright (c) 2010 Richard Lucas
@example example.php
@license http://www.opensource.org/licenses/mit-license.php
@link http://cyber.law.harvard.edu/rss/rss.html
@todo Add namespacing
The first example demonstrates an RSS feed made to share a band's demo recordings:
require_once( 'ABC_RSS.php' );
$rss = new ABC_RSS();
$channel =array();
$channel['title'] ='Band Name';
$channel['link'] ='http://www.band-name.dom/';
$channel['description'] ='This is our latest demo set.';
$channel['language'] ='en-us';
$channel['copyright'] ='Copyright 2010-2012, Band-Name';
$channel['webMaster'] ='webmaster@band-name.dom (Jane Doe)';
$channel['pubDate'] =date( DATE_RSS );
$channel['generator'] ='ABC_RSS PHP Class';
$channel['docs'] ='http://blogs.law.harvard.edu/tech/rss';
$channel['category'] =array();
$channel['category'][] =array( 'content' =>'Rock', 'domain' =>'genre' );
$channel['category'][] =array( 'content' =>'Folk', 'domain' =>'genre' );
$rss->set_channel( $channel );
$item =array();
$item['title'] ='Love Machine';
$item['description'] ='This is a power ballad evocative of MC Bubblicious Fish-eyes.';
$item['link'] ='http://www.band-name.dom/band/songs/love_machine.html
$item['enclosure'] =array();
$item['enclosure']['url'] ='http://www.band-name.dom/band/songs/love_machine.mp3';
$item['enclosure']['length'] ='12216320';
$item['enclosure']['type'] ='audio/mpeg';
$item['category'] = array();
$item['category'][] =array( 'content' =>'Rock', 'domain' =>'genre' );
$rss->set_item( $item );
$item =array();
$item['title'] ='Mud Puppet Square Dance';
$item['description'] ='A folksy taste of Americana';
$item['link'] ='http://www.band-name.dom/band/songs/mud_puppet.html
$item['enclosure'] =array();
$item['enclosure']['url'] ='http://www.band-name.dom/band/songs/mud_puppet.mp3';
$item['enclosure']['length'] ='14005740';
$item['enclosure']['type'] ='audio/mpeg';
$item['category'] = array();
$item['category'][] =array( 'content' =>'Folk', 'domain' =>'genre' );
$rss->set_item( $item );
$rss->export();
Instantiate the object...
$rss =new ABC_RSS();
Create an associative array and populate it with the elements you want for the channel. You must supply it with at least the required elements, or you will get an error...
$channel = array();
$channel['link'] ='http://www.band-name.dom/';
$channel['description'] ='This is our latest demo set.';
$channel['language'] ='en-us';
$channel['copyright'] ='Copyright 2010-2012, Band-Name';
$channel['webMaster'] ='webmaster@band-name.dom (Jane Doe)';
$channel['pubDate'] =date( DATE_RSS );
$channel['generator'] ='ABC_RSS PHP Class';
$channel['docs'] ='http://blogs.law.harvard.edu/tech/rss';
The <category> element must be handled differently from other elements, since more than one category element is allowed. For every category element, a subarray is added to the $channel['category'] element. The 'domain' attribute is optional, and the subarray element key 'domain' may be ommitted. For example, if you wanted to add the following category elements to the channel portion of your feed:
<category domain='genre'>Rock</category>
<category domain='genre'>Folk</category>
Then you would set the values like so:
$channel['category'] =array();
$channel['category'][] =array( 'content' =>'Rock', 'domain' =>'genre' );
$channel['category'][] =array( 'content' =>'Folk', 'domain' =>'genre' );
Set the channel data...
$rss->set_channel( $channel );
At least one item is required. Create a new item array for every item you want to add...
$item = array();
$item['title'] ='Love Machine';
$item['description'] ='This is a power ballad evocative of MC Bubblicious Fish-eyes.';
$item['link'] ='http://www.band-name.dom/band/songs/love_machine.html
Next is an example of an element with attributes. In such case, assign the element name an array value, and the elements of this sub-array will map to the attributes of the given element.
$item['enclosure'] =array();
$item['enclosure']['url'] ='http://www.band-name.dom/band/songs/love_machine.mp3';
$item['enclosure']['length'] ='12216320';
$item['enclosure']['type'] ='audio/mpeg';
Set the category (or categories)::
$item['category'] =array();
$item['category'][] =array( 'content' = >'Rock', 'domain' =>'genre' );
Then set each item after you have populated it:
$rss->set_item( $item );
The composition of each item can be different. Technically, neither the 'title' nor the 'description' element are absolutely required, but the specs require that you do use at least one or the other. Most RSS parsers and default display schemes will not take kindly to a missing 'title' element, however, and it's just good form to make use of both of these tags. If you insist on omitting a title, you must provide a description, however, and vice versa.
The final step is to call the 'export' function. It can take up to two optional parameters. The first parameter determines how the RSS is expressed.
The default method is to send headers telling the browser to expect content type 'text/xml', and then to exit and dump the XML content into the browser window. I would prefer to use a more specific content type, but Firefox will try to download it by default if another, more specific content-type is specified. To simply export the RSS to the browser window as described, call the export function like so:
$rss->export();
You can attempt to force the browser to download the XML rather than to display it by calling the export function and passing it the parameter 'download':
$rss->export('download');
The final option is to write the XML to file and serve it up statically. To do this, call the export function and pass it two parameters. The first tells the function to write the XML to file, and the second is the full path to the XML file you want it to write. If the file does not exist, the function will attempt to create it. You must, of course, have write permissions in order to accomplish this:
$rss->export('write','/home/user/htdocs/xml/rss.xml');
This is a more advanced example, and it portrays the use of the ABC_RSS class to fetch the latest ten local news items from a MySQL database and then write them to a static XML/RSS feed.
require_once( 'ABC_RSS.php' );
$hostname ="host";
$database ="database";
$username ="username";
$password ="password";
$errors =array();
$destination_file ='home/user/htdocs/xml/local_news.xml';
try {
if ( !$connection =mysql_connect( $hostname, $username, $password ))
throw new Exception( mysql_error(), mysql_errno() );
if ( !mysql_select_db( $database, $connection ))
throw new Exception( "Unable to connect to database '$database'." );
$query ="SELECT `id`, `title`, `description`, `datetime` ".
"FROM `articles` ".
"WHERE `scope` = 'local' ".
"ORDER BY `datetime` DESC ".
"LIMIT 0, 10";
if ( !$result =mysql_query( $query, $connection ))
throw new Exception( mysql_error(), mysql_errno() );
$row_count =@mysql_num_rows( $result );
if ( $row_count > 0 )
{
$rss =new ABC_RSS();
$channel =array();
$channel['title'] ='News.dom - All the News You Can Stand';
$channel['link'] ='http://www.news.dom/';
$channel['description'] ='The Latest Ten Local News Stories from News.dom';
$channel['language'] ='en-us';
$channel['copyright'] ='Copyright 2010-2012, News.dom';
$channel['managingEditor'] ='editor@news.dom (John Smith)';
$channel['webMaster'] ='webmaster@news.dom (Jane Doe)';
$channel['pubDate'] =date(DATE_RSS);
$channel['generator'] ='ABC_RSS PHP Class';
$channel['docs'] ='http://blogs.law.harvard.edu/tech/rss';
$channel['skipHours'] ='0,1,2,3';
$channel['skipDays'] ='Saturday';
$channel['category'] =array();
$channel['category'][] =array( 'content' =>'news', 'domain' =>'local' );
if ( !$rss->set_channel( $channel ))
{
$errors =&$rss->errors;
throw new Exception( "Failed to set the channel." );
}
for ( $i =0 ; $i <$row_count ; $i++ )
{
$row =mysql_fetch_assoc( $result );
$item =array();
$item['title'] =$row['title'];
$item['description'] =$row['description'];
$item['link'] =sprintf( "http://www.news.dom/articles/?id=%d", $row['id'] );
$item['pubDate'] =date( DATE_RSS, strtotime( $row['datetime'] ));
$item['category'] =array();
$item['category'][] =array( 'content' =>'news', 'domain' =>'local' );
if ( !$rss->set_item( $item ))
{
$errors =&$rss->errors;
throw new Exception( "Failed to set the item." );
}
}
if ( !$rss->export( 'write', $destination_file ))
{
$errors =&$rss->errors;
throw new Exception( "Failed to write the XML to file." );
}
}
else throw new Exception( "No articles were found in the database." );
}
catch ( Exception $e ) { $errors[] =$e->getMessage(); }
if ( count( $errors ) > 0 )
foreach ( $errors as $error )
printf( "<p>%s</p>", $error );
else
exit ( "<p>The XML file was written without errors.</p>" );
In Example 2 there are two optional elements which are handled slightly differently than the elements shown above. The elements <skipHours> and <skipDays> describe to feed aggregators the hours and days upon which the XML cannot be refreshed. The <skipHours> element takes up to 23 sub-elements, each named <hour>, and each containing an integer value between 0 and 24 inclusive representing the hours of the day. To instruct aggregators not to attempt to refresh the feed between the hours of midnight and four o'clock in the morning, for example, you might try the following:
<skipHours>
<hour>0</hour>
<hour>1</hour>
<hour>2</hour>
<hour>3</hour>
</skipHours>
Obviously, you cannot build the <skipHours> element the same way that you build other elements, because you cannot have multiple keys with the name 'hour' in the array. The <skipDays> element is similar, only instead of integers it takes the name of the days the aggregaotrs should skip:
<skipDays>
<day>Saturday</day>
</skipDays>
But we still have the problem of a repeating element name which cannot be represented as a repeating array key, since each successive array element having the same key, 'day', would simply overwrite the previous one.
So, for these two optional elements, the solution is very simple. You'll pass the repeating XML values into one array value using a comma-separated list. In order to recreate the <skipHours> structure that you see above, you would create a corresponding array element and feed in the comma-delimited string:
$channel['skipHours'] = '0,1,2,3';
And for the <skipDays> element:
$channel['skipDays'] = 'Saturday';
Here is an example of how it might look if you created an RSS with every required and optional element.
require_once( 'ABC_RSS.php' );
$destination_file ='rss.xml';
$rss = new ABC_RSS();
try {
// All possible channel elements
$channel =array();
$channel['title'] ='News.dom';
$channel['link'] ='http://www.news.dom/';
$channel['description'] ='All the News You Can Stand';
$channel['language'] ='en-us';
$channel['copyright'] ='Copyright 2010-2012, News.dom';
$channel['managingEditor'] ='editor@news.dom (John Smith)';
$channel['webMaster'] ='webmaster@news.dom (Jane Doe)';
$channel['pubDate'] =date( DATE_RSS );
$channel['lastBuildDate'] =date( DATE_RSS );
$channel['category'] =array();
$channel['category'][] =array( 'content' =>'news', 'domain' =>'local,regional,national,world' );
$channel['category'][] =array( 'content' =>'sports', 'domain' =>'local,regional,national' );
$channel['category'][] =array( 'content' =>'entertainment', 'domain' =>'local,regional,national' );
$channel['generator'] ='ABC_RSS PHP Class';
$channel['docs'] ='http://blogs.law.harvard.edu/tech/rss';
$channel['cloud'] = array();
$channel['cloud']['domain'] ='rpc.news.dom';
$channel['cloud']['port'] ='80';
$channel['cloud']['path'] ='/RPC2';
$channel['cloud']['registerProcedure'] ='pingMe';
$channel['cloud']['protocol'] ='soap';
$channel['ttl'] = '60';
$channel['image'] =array();
$channel['image']['url'] ='http://www.news.dom/logo.jpg';
$channel['image']['title'] ='News.dom';
$channel['image']['link'] ='http://www.news.dom/';
$channel['image']['width'] ='88';
$channel['image']['height'] ='31';
$channel['rating'] ='(PICS-1.1 "http://www.gcf.org/v2.5" '.
'l r (suds 0.5 density 0 color/hue 1) '.
'r (subject 2 density 1 color/hue 1))';
$channel['textInput'] =array();
$channel['textInput']['title'] ='Search';
$channel['textInput']['description'] ='Find an article on News.dom';
$channel['textInput']['name'] ='q';
$channel['textInput']['link'] ='http://www.news.dom/search/';
$channel['skipHours'] ='0,1,2,3';
$channel['skipDays'] ='Saturday';
if ( !$rss->set_channel( $channel ))
{
$errors =&$rss->errors;
throw new Exception( 'Unable to set the RSS channel.' );
}
// All possible item elements
$item =array();
$item['title'] ='New Water Park Makes Big Splash in Local Economy';
$item['description'] ='Reporter Peter Griffin talks to Bob Smith, the owner-operator '.
'of Quahog\'s new attraction, Wetworld.';
$item['link'] ='http://www.news.dom/headlines/';
$item['author'] ='Peter Griffin';
$item['category'] =array();
$item['category'][] =array( 'content' =>'news', 'domain' =>'local' );
$item['category'][] =array( 'content' =>'entertainment', 'domain' =>'local' );
$item['comments'] ='http://www.news.dom/comments/?article=7836453';
$item['enclosure'] =array();
$item['enclosure']['url'] ='http://www.news.dom/video/?id=68737';
$item['enclosure']['length'] ='12216320';
$item['enclosure']['type'] ='video/quicktime';
$item['guid'] =array();
$item['guid']['isPermaLink'] ='true';
$item['guid']['content'] ='http://www.news.dom/articles/?id=7836453';
$item['pubDate'] ='Sun, 19 May 2002 15:21:36 GMT';
if ( !$rss->set_item( $item ))
{
$errors =&$rss->errors;
throw new Exception( 'Unable to set the RSS item.' );
}
if ( !$rss->export( 'write', $destination_file ))
{
$errors =&$rss->errors;
throw new Exception( 'Unable to write the RSS file.' );
}
}
catch ( Exception $e ) { $errors[] =$e->getMessage(); }
if ( !empty( $errors ))
foreach ( $errors as $error )
printf( "<p>%s</p>", $error );
else
print '<p>The RSS was created without errors.</p>';