<?php if (!defined('PmWiki')) exit(); /* Copyright 2002-2004 Patrick R. Michaud (pmichaud@pobox.com) This file is part of PmWiki; 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 2 of the License, or (at your option) any later version. See pmwiki.php for full details. This script defines "?action=rss". It will read the current page for a WikiTrail, and then output an RSS 2.0 document with the current page as the channel and the pages in the WikiTrail as the items. To add RSS capabilities to your site, simply add to config.php: include_once('scripts/rss.php'); To avoid the cost of loading this script and initializing RSS variables when you aren't going to generate a feed, use this instead: if ($action == 'rss' || $action == 'rdf') include_once('scripts/rss.php'); Added by Crisses: Functionality for MP3 enclosures (such as those used for "podcasting" feeds.): 1) requires uploads enabled before calling rss.php 2) requires $EnableRssEnclosures = 1; to be set before calling rss.php my settings in config.php -- note that I put the altered rss.php in my farm's local dir: // Begin podcasting setup only for group MyPodcasts if (preg_match('/^MyPodcasts\//',$pagename)){ $EnableUpload = 1; $DefaultPasswords['upload'] = crypt('mysecretandyoucanthaveit'); $EnableRssEnclosures = 1; } // load alternate rss.php if ($action == 'rss' || $action == 'rdf') include_once("$FarmD/local/rss.php"); Note that this enables normal RSS for the entire site except the MyPodcasts group, which has the addition of uploads and the enclosures necessary for podcasting. **Additional work would need to be done to enable file types other than mp3.** */ //version added by Crisses define(RSS_VERSION, '0.5'); // with the caveat that rss 0.1 was PM's original version SDV($HandleActions['rss'],'HandleRss'); SDV($HandleActions['rdf'],'HandleRss'); SDV($RssMaxItems,20); # maximum items to display SDV($RssSourceSize,400); # max size to build desc from SDV($RssDescSize,200); # max desc size SDV($RssItems,array()); # RSS item elements SDV($RssItemsRDFList,array()); # RDF <items> elements // More variables added by Crisses SDV($RssFeedTitle, "$WikiTitle | $Group / $Title"); # Allows custom feed titles dependent on wiki, group, or page variables. SDV($RssFeedDesc, '$RssChannelDesc'); // defaults to prior behavior of reading descriptions from the file SDV($RssFeedDescFromMetadata, 0); // to allow taking feed descriptions from metadata (:description:) wiki-tag SDV($RssItemDescFromMetadata, 0); // to allow taking item descriptions from metadata (:description:) wiki-tag SDV($RssItemTitleOnly, 0); // removes the $Group from the RSS item's title SDV($EnableRssEnclosures, 0); // allows rss-feed attachments SDV($RssFeedOptions, ''); // user-configurable RSS feed options SDV($RssItemOptions, ''); // gives people the ability to add optional tags in their config.php files (like copyright info) SDV($RssEnclosureTLA, 'mp3'); // default attachments are mp3 SDV($RssEmailAddress, ''); // default value for feed email address // modified original RSS xml standards to have xml comments, which will be replaced later. // The reason for xml comments is so that the feed won't outright break if the comments are not removed. if ($action=='rdf') { ### RSS 1.0 (RDF) definitions SDV($RssTimeFmt,'%Y-%m-%dT%H:%MZ'); # time format SDV($RssItemsRDFListFmt,"<rdf:li rdf:resource=\"\$PageUrl\" />\n"); SDV($RssChannelFmt,array('<?xml version="1.0"?'.'> <rdf:RDF xmlns="http://purl.org/rss/1.0/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/"> <channel rdf:about="$PageUrl"> <!-- feed title here --> <link>$PageUrl</link> <!-- feed description here --> <dc:date>$RssChannelBuildDate</dc:date> <!-- feed options here --> <items> <rdf:Seq>',&$RssItemsRDFList,' </rdf:Seq> </items> </channel>')); SDV($RssItemFmt,' <item rdf:about="$PageUrl"> <!-- item title here --> <link>$PageUrl</link> <!-- item description here --> <description>$RssItemDesc</description> <pubDate>$RssItemPubDate</pubDate> <!-- item options here --> </item>'); // Run subroutine that alters the format variables above ApplyRssOptions(); SDV($HandleRssFmt,array(&$RssChannelFmt,&$RssItems,'</rdf:RDF>')); } ### RSS 2.0 definitions SDV($RssTimeFmt,'%a, %d %b %Y %H:%M:%S GMT'); SDV($RssChannelFmt,'<?xml version="1.0"?'.'> <rss version="2.0"> <channel> <!-- feed title here --> <link>$PageUrl</link> <!-- feed description here --> <lastBuildDate>$RssChannelBuildDate</lastBuildDate> <!-- feed options here --> <docs>http://blogs.law.harvard.edu/tech/rss</docs> <generator>PmWiki $Version</generator>'); SDV($RssItemFmt,' <item> <!-- item title here --> <link>$PageUrl</link> <!-- item description here --> <author>' . $RssEmailAddress . ' ($RssItemAuthor)</author> <pubDate>$RssItemPubDate</pubDate> <!-- item options here --> </item>'); // Run subroutine that alters the format variables above ApplyRssOptions(); SDV($HandleRssFmt,array(&$RssChannelFmt,&$RssItems,' </channel> </rss>')); function ApplyRssOptions(){ global $RssChannelFmt, $RssItemFmt, $RssItemTitleOnly, $EnableRssEnclosures, $RssItemTitleOnly, $RssFeedDescFromMetadata, $RssItemOptions; // this function applies user & default variable data in place of comment tags in the RSS feed information //load the correct RSS Item Titles depending on whether title-only is enabled. if ($RssItemTitleOnly==1) { $RssItemFmt = str_replace ('<!-- item title here -->', '<title>$Title</title>', $RssItemFmt); } else { $RssItemFmt = str_replace ('<!-- item title here -->', '<title>$Group / $Title</title>', $RssItemFmt); } // add in the Item Options, with variation based on whether enclosures are enabled or not. // enclosures are only an option in RSS 2.0 -- so this section allows them for only an RSS 2.0 feed if ($EnableRssEnclosures && $action='rss'){ $RssItemOptions = '<enclosure url="RssItemEnclosureUrl" length="$RssItemEnclosureLength" type="$RssItemEnclosureType"/> ' . $RssItemOptions; } $RssItemFmt = str_replace ('<!-- item options here -->', $RssItemOptions, $RssItemFmt); // creating a variable reference to point to the format portion of interest // in RSS 1.0 it's in an array -- in 2.0 it is not. if ($action=='rdf') { $feed_format_pointer =& $RssChannelFmt[0]; } else { $feed_format_pointer =& $RssChannelFmt; } // to allow taking feed descriptions from metadata (:description:) wiki-tag [Pending Feature] $feed_format_pointer = str_replace ('<!-- feed title here -->', '<title>$RssFeedTitle</title>', $feed_format_pointer); $feed_format_pointer = str_replace ('<!-- feed options here -->', '$RssFeedOptions', $feed_format_pointer); if ($RssFeedDescFromMetadata == 1){ //Right now this does nothing -- I am waiting for information from Patrick $feed_format_pointer = str_replace ('<!-- feed description here -->', '<description>$RssFeedDesc</description>', $feed_format_pointer); } else { $feed_format_pointer = str_replace ('<!-- feed description here -->', '<description>$RssFeedDesc</description>', $feed_format_pointer); } // to allow taking descriptions from metadata (:description:) wiki-tag [Pending Feature] If ($RssItemDescFromMetadata == 1) { // This does nothing until I hear from Patrick $RssItemFmt = str_replace ('<!-- item description here -->', '<description>$RssItemDesc</description>', $RssItemFmt); } else { $RssItemFmt = str_replace ('<!-- item description here -->', '<description>$RssItemDesc</description>', $RssItemFmt); } } // end function ApplyRssOptions function HandleRss($pagename) { global $RssMaxItems,$RssSourceSize,$RssDescSize, $RssChannelFmt,$RssChannelDesc,$RssTimeFmt,$RssChannelBuildDate, $RssItemsRDFList,$RssItemsRDFListFmt,$RssItems,$RssItemFmt, $HandleRssFmt,$FmtV; // Added by Crisses to detect if enclosures are enabled global $EnableRssEnclosures; // this array grabs the items in the trail $trailpage = ReadTrail($pagename,$pagename); // retrieve page text if authorized to see the page $page = RetrieveAuthPage($pagename,'read',false); // if it didn't grab the page, abort with reason. if (!$page) Abort("?cannot read $pagename"); // grabs page timestamp $cbgmt = $page['time']; // define an array to hold an array of info for each article/item in the feed $itemarray = array(); // grabs the existing trail items up to the number of max items in the feed for($i=0;$i<count($trailpage) && count($itemarray)<$RssMaxItems;$i++) { if (!PageExists($trailpage[$i]['pagename'])) continue; $page = RetrieveAuthPage($trailpage[$i]['pagename'],'read',false); Lock(0); if (!$page) continue; // grabs the page text characters only up to RSS Source Size var. $text = MarkupToHTML($trailpage[$i]['pagename'],substr($page['text'],0,$RssSourceSize)); // Note to self / Crisses // This area picks out the description for the Items? Where is the desc for // the feed picked up? $text = entityencode(preg_replace("/<.*?>/s","",$text)); preg_match("/^(.{0,$RssDescSize}\\s)/s",$text,$match); $itemarray[] = array('name' => $trailpage[$i]['pagename'],'time' => $page['time'], 'desc' => $match[1]." ...", 'author' => $page['author']); if ($page['time']>$cbgmt) $cbgmt=$page['time']; } SDV($RssChannelBuildDate, entityencode(gmdate('D, d M Y H:i:s \G\M\T', $cbgmt))); // Here is the channel description SDV($RssChannelDesc,entityencode(FmtPageName('$Group.$Title',$pagename))); // Item encoding here // iterates through each page in the feed (ie Items) foreach($itemarray as $page) { $FmtV['$RssItemPubDate'] = gmstrftime($RssTimeFmt,$page['time']); $FmtV['$RssItemDesc'] = $page['desc']; $FmtV['$RssItemAuthor'] = $page['author']; // Create a temporary item format that can be altered in the iteration of each page $TempRssItemFmt = $RssItemFmt; // Added by Crisses // Provide the additional variables for the enclosure format if ($EnableRssEnclosures) { // grab the necessary variables global $UploadDir,$UploadUrlFmt,$UploadExts,$RssEnclosureTLA; // find the subdir & name of the enclosure file $EnclosureFileName = str_replace(".", "/", $page['name']) . '.' . $RssEnclosureTLA; // The machine's path to the file $FmtV['RssItemEnclosureUrl'] = "$UploadUrlFmt/$EnclosureFileName"; $filePath = "$UploadDir/$EnclosureFileName"; // Only look for enclosure information if the file exists if (file_exists($filePath)) { // Determine the length of the enclosure (required) $FmtV['$RssItemEnclosureLength'] = filesize("$UploadDir/$EnclosureFileName"); // Give the file type $FmtV['$RssItemEnclosureType'] = $UploadExts["$RssEnclosureTLA"]; } else { // if there is no attachment, remove the enclosure formatting only for the current item $TempRssItemFmt = preg_replace ('!<enclosure url.*/>!', "", $TempRssItemFmt); } } // End (Added by Crisses) $RssItemsRDFList[] = entityencode(FmtPageName($RssItemsRDFListFmt,$page['name'])); $RssItems[] = entityencode(FmtPageName($TempRssItemFmt,$page['name'])); } header("Content-type: text/xml"); PrintFmt($pagename,$HandleRssFmt); exit(); } # entityencode() and $EntitiesTable are used to convert non-ASCII characters # and named entities into numeric entities, since the RSS and RDF # specifications don't have a good way of incorporating them by default. function entityencode($s) { global $EntitiesTable; $s = str_replace(array_keys($EntitiesTable),array_values($EntitiesTable),$s); return preg_replace('/([\\x80-\\xff])/e',"'&#'.ord('\$1').';'",$s); } SDVA($EntitiesTable, array( # entities defined in "http://www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent" ' ' => ' ', '¡' => '¡', '¢' => '¢', '£' => '£', '¤' => '¤', '¥' => '¥', '¦' => '¦', '§' => '§', '¨' => '¨', '©' => '©', 'ª' => 'ª', '«' => '«', '¬' => '¬', '­' => '­', '®' => '®', '¯' => '¯', '°' => '°', '±' => '±', '²' => '²', '³' => '³', '´' => '´', 'µ' => 'µ', '¶' => '¶', '·' => '·', '¸' => '¸', '¹' => '¹', 'º' => 'º', '»' => '»', '¼' => '¼', '½' => '½', '¾' => '¾', '¿' => '¿', 'À' => 'À', 'Á' => 'Á', 'Â' => 'Â', 'Ã' => 'Ã', 'Ä' => 'Ä', 'Å' => 'Å', 'Æ' => 'Æ', 'Ç' => 'Ç', 'È' => 'È', 'É' => 'É', 'Ê' => 'Ê', 'Ë' => 'Ë', 'Ì' => 'Ì', 'Í' => 'Í', 'Î' => 'Î', 'Ï' => 'Ï', 'Ð' => 'Ð', 'Ñ' => 'Ñ', 'Ò' => 'Ò', 'Ó' => 'Ó', 'Ô' => 'Ô', 'Õ' => 'Õ', 'Ö' => 'Ö', '×' => '×', 'Ø' => 'Ø', 'Ù' => 'Ù', 'Ú' => 'Ú', 'Û' => 'Û', 'Ü' => 'Ü', 'Ý' => 'Ý', 'Þ' => 'Þ', 'ß' => 'ß', 'à' => 'à', 'á' => 'á', 'â' => 'â', 'ã' => 'ã', 'ä' => 'ä', 'å' => 'å', 'æ' => 'æ', 'ç' => 'ç', 'è' => 'è', 'é' => 'é', 'ê' => 'ê', 'ë' => 'ë', 'ì' => 'ì', 'í' => 'í', 'î' => 'î', 'ï' => 'ï', 'ð' => 'ð', 'ñ' => 'ñ', 'ò' => 'ò', 'ó' => 'ó', 'ô' => 'ô', 'õ' => 'õ', 'ö' => 'ö', '÷' => '÷', 'ø' => 'ø', 'ù' => 'ù', 'ú' => 'ú', 'û' => 'û', 'ü' => 'ü', 'ý' => 'ý', 'þ' => 'þ', 'ÿ' => 'ÿ', # entities defined in "http://www.w3.org/TR/xhtml1/DTD/xhtml-special.ent" '"' => '"', #'&' => '&#38;', #'<' => '&#60;', #'>' => '>', ''' => ''', 'Œ' => 'Œ', 'œ' => 'œ', 'Š' => 'Š', 'š' => 'š', 'Ÿ' => 'Ÿ', 'ˆ' => 'ˆ', '˜' => '˜', ' ' => ' ', ' ' => ' ', ' ' => ' ', '‌' => '‌', '‍' => '‍', '‎' => '‎', '‏' => '‏', '–' => '–', '—' => '—', '‘' => '‘', '’' => '’', '‚' => '‚', '“' => '“', '”' => '”', '„' => '„', '†' => '†', '‡' => '‡', '‰' => '‰', '‹' => '‹', '›' => '›', '€' => '€', # entities defined in "http://www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent" 'ƒ' => 'ƒ', 'Α' => 'Α', 'Β' => 'Β', 'Γ' => 'Γ', 'Δ' => 'Δ', 'Ε' => 'Ε', 'Ζ' => 'Ζ', 'Η' => 'Η', 'Θ' => 'Θ', 'Ι' => 'Ι', 'Κ' => 'Κ', 'Λ' => 'Λ', 'Μ' => 'Μ', 'Ν' => 'Ν', 'Ξ' => 'Ξ', 'Ο' => 'Ο', 'Π' => 'Π', 'Ρ' => 'Ρ', 'Σ' => 'Σ', 'Τ' => 'Τ', 'Υ' => 'Υ', 'Φ' => 'Φ', 'Χ' => 'Χ', 'Ψ' => 'Ψ', 'Ω' => 'Ω', 'α' => 'α', 'β' => 'β', 'γ' => 'γ', 'δ' => 'δ', 'ε' => 'ε', 'ζ' => 'ζ', 'η' => 'η', 'θ' => 'θ', 'ι' => 'ι', 'κ' => 'κ', 'λ' => 'λ', 'μ' => 'μ', 'ν' => 'ν', 'ξ' => 'ξ', 'ο' => 'ο', 'π' => 'π', 'ρ' => 'ρ', 'ς' => 'ς', 'σ' => 'σ', 'τ' => 'τ', 'υ' => 'υ', 'φ' => 'φ', 'χ' => 'χ', 'ψ' => 'ψ', 'ω' => 'ω', 'ϑ' => 'ϑ', 'ϒ' => 'ϒ', 'ϖ' => 'ϖ', '•' => '•', '…' => '…', '′' => '′', '″' => '″', '‾' => '‾', '⁄' => '⁄', '℘' => '℘', 'ℑ' => 'ℑ', 'ℜ' => 'ℜ', '™' => '™', 'ℵ' => 'ℵ', '←' => '←', '↑' => '↑', '→' => '→', '↓' => '↓', '↔' => '↔', '↵' => '↵', '⇐' => '⇐', '⇑' => '⇑', '⇒' => '⇒', '⇓' => '⇓', '⇔' => '⇔', '∀' => '∀', '∂' => '∂', '∃' => '∃', '∅' => '∅', '∇' => '∇', '∈' => '∈', '∉' => '∉', '∋' => '∋', '∏' => '∏', '∑' => '∑', '−' => '−', '∗' => '∗', '√' => '√', '∝' => '∝', '∞' => '∞', '∠' => '∠', '∧' => '∧', '∨' => '∨', '∩' => '∩', '∪' => '∪', '∫' => '∫', '∴' => '∴', '∼' => '∼', '≅' => '≅', '≈' => '≈', '≠' => '≠', '≡' => '≡', '≤' => '≤', '≥' => '≥', '⊂' => '⊂', '⊃' => '⊃', '⊄' => '⊄', '⊆' => '⊆', '⊇' => '⊇', '⊕' => '⊕', '⊗' => '⊗', '⊥' => '⊥', '⋅' => '⋅', '⌈' => '⌈', '⌉' => '⌉', '⌊' => '⌊', '⌋' => '⌋', '⟨' => '〈', '⟩' => '〉', '◊' => '◊', '♠' => '♠', '♣' => '♣', '♥' => '♥', '♦' => '♦')); ?>