<?php if (!defined('PmWiki')) exit(); /* Copyright 2005-2007 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 provides a number of syndication feed and xml-based metadata options to PmWiki, including Atom, RSS 2.0, RSS 1.0 (RDF), and the Dublin Core Metadata extensions. This module is typically activated from a local configuration file via a line such as if ($action == 'atom') include_once("$FarmD/scripts/feeds.php"); if ($action == 'dc') include_once("$FarmD/scripts/feeds.php"); When enabled, ?action=atom, ?action=rss, and ?action=rdf produce syndication feeds based on any wikitrail contained in the page, or, for Category pages, on the pages in the category. The feeds are generated using pagelist, thus one can include parameters such as count=, list=, order=, etc. in the url to adjust the feed output. ?action=dc will normally generate Dublin Core Metadata for the current page only, but placing a group=, trail=, or link= argument in the url causes it to generate metadata for all pages in the associated group, trail, or backlink. There are a large number of customizations available, most of which are controlled by the $FeedFmt array. Elements $FeedFmt look like $FeedFmt['atom']['feed']['rights'] = 'All Rights Reserved'; where the first index corresponds to the action (?action=atom), the second index indicates a per-feed or per-item element, and the third index is the name of the element being generated. The above setting would therefore generate a "<rights>All Rights Reserved</rights>" in the feed for ?action=atom. If the value of an entry begins with a '<', then feeds.php doesn't automatically add the tag around it. Elements can also be callable functions which are called to generate the appropriate output. For example, to set the RSS 2.0 <author> element to the value of the last author to modify a page, one can set (in local/config.php): $FeedFmt['rss']['item']['author'] = '$LastModifiedBy'; To use the RSS 2.0 <description> element to contain the change summary of the most recent edit, set $FeedFmt['rss']['item']['description'] = '$LastModifiedSummary'; Feeds.php can also be combined with attachments to support podcasting via ?action=rss. Any page such as "PageName" that has an mp3 attachment with the same name as the page ("PageName.mp3") will have an appropriate <enclosure> element in the feed output. The set of allowed attachments can be extended using the $RSSEnclosureFmt array: $RSSEnclosureFmt = array('{$Name}.mp3', '{$Name}.mp4'); References: http://www.atomenabled.org/developers/syndication/ http://dublincore.org/documents/dcmes-xml/ http://en.wikipedia.org/wiki/Podcasting */ ## Settings for ?action=atom SDVA($FeedFmt['atom']['feed'], array( '_header' => 'Content-type: text/xml; charset="$Charset"', '_start' => '<?xml version="1.0" encoding="$Charset"?'.'> <feed xmlns="http://www.w3.org/2005/Atom">'."\n", '_end' => "</feed>\n", 'title' => '$WikiTitle', 'link' => '<link rel="self" href="{$PageUrl}?action=atom" />', 'id' => '{$PageUrl}?action=atom', 'updated' => '$FeedISOTime', 'author' => "<author><name>$WikiTitle</name></author>\n", 'generator' => '$Version', 'logo' => '$PageLogoUrl')); SDVA($FeedFmt['atom']['item'], array( '_start' => "<entry>\n", 'id' => '{$PageUrl}', 'title' => '{$Title}', 'updated' => '$ItemISOTime', 'link' => "<link rel=\"alternate\" href=\"{\$PageUrl}\" />\n", 'author' => "<author><name>{\$LastModifiedBy}</name></author>\n", 'summary' => '{$Description}', 'category' => "<category term=\"\$Category\" />\n", '_end' => "</entry>\n")); ## Settings for ?action=dc SDVA($FeedFmt['dc']['feed'], array( '_header' => 'Content-type: text/xml; charset="$Charset"', '_start' => '<?xml version="1.0" encoding="$Charset"?'.'> <!DOCTYPE rdf:RDF PUBLIC "-//DUBLIN CORE//DCMES DTD 2002/07/31//EN" "http://dublincore.org/documents/2002/07/31/dcmes-xml/dcmes-xml-dtd.dtd"> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">'."\n", '_end' => "</rdf:RDF>\n")); SDVA($FeedFmt['dc']['item'], array( '_start' => "<rdf:Description rdf:about=\"{\$PageUrl}\">\n", 'dc:title' => '{$Title}', 'dc:identifier' => '{$PageUrl}', 'dc:date' => '$ItemISOTime', 'dc:type' => 'Text', 'dc:format' => 'text/html', 'dc:description' => '{$Description}', 'dc:subject' => "<dc:subject>\$Category</dc:subject>\n", 'dc:publisher' => '$WikiTitle', 'dc:author' => '{$LastModifiedBy}', '_end' => "</rdf:Description>\n")); ## RSS 2.0 settings for ?action=rss SDVA($FeedFmt['rss']['feed'], array( '_header' => 'Content-type: text/xml; charset="$Charset"', '_start' => '<?xml version="1.0" encoding="$Charset"?'.'> <rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/"> <channel>'."\n", '_end' => "</channel>\n</rss>\n", 'title' => '$WikiTitle | {$Group} / {$Title}', 'link' => '{$PageUrl}?action=rss', 'description' => '{$Group}.{$Title}', 'lastBuildDate' => '$FeedRSSTime')); SDVA($FeedFmt['rss']['item'], array( '_start' => "<item>\n", '_end' => "</item>\n", 'title' => '{$Group} / {$Title}', 'link' => '{$PageUrl}', 'description' => '{$Description}', 'dc:contributor' => '{$LastModifiedBy}', 'dc:date' => '$ItemISOTime', 'pubDate' => '$ItemRSSTime', 'enclosure' => 'RSSEnclosure')); ## RDF 1.0, for ?action=rdf SDVA($FeedFmt['rdf']['feed'], array( '_header' => 'Content-type: text/xml; charset="$Charset"', '_start' => '<?xml version="1.0" encoding="$Charset"?'.'> <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}?action=rdf">'."\n", 'title' => '$WikiTitle | {$Group} / {$Title}', 'link' => '{$PageUrl}?action=rdf', 'description' => '{$Group}.{$Title}', 'dc:date' => '$FeedISOTime', 'items' => "<items>\n<rdf:Seq>\n\$FeedRDFSeq</rdf:Seq>\n</items>\n", '_items' => "</channel>\n", '_end' => "</rdf:RDF>\n")); SDVA($FeedFmt['rdf']['item'], array( '_start' => "<item rdf:about=\"{\$PageUrl}\">\n", '_end' => "</item>\n", 'title' => '$WikiTitle | {$Group} / {$Title}', 'link' => '{$PageUrl}', 'description' => '{$Description}', 'dc:date' => '$ItemISOTime')); foreach(array_keys($FeedFmt) as $k) { SDV($HandleActions[$k], 'HandleFeed'); SDV($HandleAuth[$k], 'read'); } function HandleFeed($pagename, $auth = 'read') { global $FeedFmt, $action, $PCache, $FmtV, $TimeISOZFmt, $RSSTimeFmt, $FeedPageListOpt, $FeedCategoryOpt, $FeedTrailOpt, $FeedDescPatterns, $CategoryGroup, $EntitiesTable; SDV($RSSTimeFmt, 'D, d M Y H:i:s \G\M\T'); SDV($FeedDescPatterns, array('/<[^>]*$/' => ' ', '/\\w+$/' => '', '/<[^>]+>/' => '')); $FeedPageListOpt = (array)@$FeedPageListOpt; SDVA($FeedCategoryOpt, array('link' => $pagename)); SDVA($FeedTrailOpt, array('trail' => $pagename, 'count' => 10)); $f = $FeedFmt[$action]; $page = RetrieveAuthPage($pagename, $auth, true, READPAGE_CURRENT); if (!$page) Abort("?cannot generate feed"); $feedtime = $page['time']; # determine list of pages to display if (@($_REQUEST['trail'] || $_REQUEST['group'] || $_REQUEST['link'] || $_REQUEST['name'])) $opt = $FeedPageListOpt; else if (preg_match("/^$CategoryGroup\\./", $pagename)) $opt = $FeedCategoryOpt; else if ($action != 'dc') $opt = $FeedTrailOpt; else { PCache($pagename, $page); $pagelist = array($pagename); } if (!@$pagelist) { $opt = array_merge($opt, @$_REQUEST); $pagelist = MakePageList($pagename, $opt, 0); } # process list of pages in feed $rdfseq = ''; $pl = array(); foreach($pagelist as $pn) { if (!PageExists($pn)) continue; if (!isset($PCache[$pn]['time'])) { $page = ReadPage($pn, READPAGE_CURRENT); PCache($pn, $page); } $pc = & $PCache[$pn]; $pl[] = $pn; if (@$opt['count'] && count($pl) >= $opt['count']) break; $rdfseq .= FmtPageName("<rdf:li resource=\"{\$PageUrl}\" />\n", $pn); if ($pc['time'] > $feedtime) $feedtime = $pc['time']; } $pagelist = $pl; $FmtV['$FeedRDFSeq'] = $rdfseq; $FmtV['$FeedISOTime'] = gmstrftime($TimeISOZFmt, $feedtime); $FmtV['$FeedRSSTime'] = gmdate($RSSTimeFmt, $feedtime); # format start of feed $out = FmtPageName($f['feed']['_start'], $pagename); # format feed elements foreach($f['feed'] as $k => $v) { if ($k{0} == '_' || !$v) continue; $x = FmtPageName($v, $pagename); if (!$x) continue; $out .= ($v{0} == '<') ? $x : "<$k>$x</$k>\n"; } #add guid to rss if requested if (@$_REQUEST['edits'] && $action == 'rss') $f['item']['guid'] = '{$PageUrl}?guid=$ItemISOTime'; # format items in feed if (@$f['feed']['_items']) $out .= FmtPageName($f['feed']['_items'], $pagename); foreach($pagelist as $pn) { $page = &$PCache[$pn]; $FmtV['$ItemDesc'] = @$page['description']; $FmtV['$ItemISOTime'] = gmstrftime($TimeISOZFmt, $page['time']); $FmtV['$ItemRSSTime'] = gmdate($RSSTimeFmt, $page['time']); $out .= FmtPageName($f['item']['_start'], $pn); foreach((array)@$f['item'] as $k => $v) { if ($k{0} == '_' || !$v) continue; if (is_callable($v)) { $out .= $v($pn, $page, $k); continue; } if (strpos($v, '$LastModifiedBy') !== false && !@$page['author']) continue; if (strpos($v, '$Category') !== false) { if (preg_match_all("/(?<=^|,)$CategoryGroup\\.([^,]+)/", @$page['targets'], $match)) { foreach($match[1] as $c) { $FmtV['$Category'] = $c; $out .= FmtPageName($v, $pn); } } continue; } $x = FmtPageName($v, $pn); if (!$x) continue; $out .= ($v{0} == '<') ? $x : "<$k>$x</$k>\n"; } $out .= FmtPageName($f['item']['_end'], $pn); } $out .= FmtPageName($f['feed']['_end'], $pagename); foreach((array)@$f['feed']['_header'] as $fmt) header(FmtPageName($fmt, $pagename)); print str_replace(array_keys($EntitiesTable), array_values($EntitiesTable), $out); } ## RSSEnclosure is called in ?action=rss to generate <enclosure> ## tags for any pages that have an attached "PageName.mp3" file. ## The set of attachments to enclose is given by $RSSEnclosureFmt. function RSSEnclosure($pagename, &$page, $k) { global $RSSEnclosureFmt, $UploadFileFmt, $UploadExts; if (!function_exists('MakeUploadName')) return ''; SDV($RSSEnclosureFmt, array('{$Name}.mp3')); $encl = ''; foreach((array)$RSSEnclosureFmt as $fmt) { $path = FmtPageName($fmt, $pagename); $upname = MakeUploadName($pagename, $path); $filepath = FmtPageName("$UploadFileFmt/$upname", $pagename); if (file_exists($filepath)) { $length = filesize($filepath); $type = @$UploadExts[preg_replace('/.*\\./', '', $filepath)]; $url = LinkUpload($pagename, 'Attach:', $path, '', '', '$LinkUrl'); $encl .= "<$k url='$url' length='$length' type='$type' />"; } } return $encl; } ## Since most feeds don't understand html character entities, we ## convert the common ones to their numeric form here. 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" 'ƒ' => 'ƒ', 'Α' => 'Α', 'Β' => 'Β', 'Γ' => 'Γ', 'Δ' => 'Δ', 'Ε' => 'Ε', 'Ζ' => 'Ζ', 'Η' => 'Η', 'Θ' => 'Θ', 'Ι' => 'Ι', 'Κ' => 'Κ', 'Λ' => 'Λ', 'Μ' => 'Μ', 'Ν' => 'Ν', 'Ξ' => 'Ξ', 'Ο' => 'Ο', 'Π' => 'Π', 'Ρ' => 'Ρ', 'Σ' => 'Σ', 'Τ' => 'Τ', 'Υ' => 'Υ', 'Φ' => 'Φ', 'Χ' => 'Χ', 'Ψ' => 'Ψ', 'Ω' => 'Ω', 'α' => 'α', 'β' => 'β', 'γ' => 'γ', 'δ' => 'δ', 'ε' => 'ε', 'ζ' => 'ζ', 'η' => 'η', 'θ' => 'θ', 'ι' => 'ι', 'κ' => 'κ', 'λ' => 'λ', 'μ' => 'μ', 'ν' => 'ν', 'ξ' => 'ξ', 'ο' => 'ο', 'π' => 'π', 'ρ' => 'ρ', 'ς' => 'ς', 'σ' => 'σ', 'τ' => 'τ', 'υ' => 'υ', 'φ' => 'φ', 'χ' => 'χ', 'ψ' => 'ψ', 'ω' => 'ω', 'ϑ' => 'ϑ', 'ϒ' => 'ϒ', 'ϖ' => 'ϖ', '•' => '•', '…' => '…', '′' => '′', '″' => '″', '‾' => '‾', '⁄' => '⁄', '℘' => '℘', 'ℑ' => 'ℑ', 'ℜ' => 'ℜ', '™' => '™', 'ℵ' => 'ℵ', '←' => '←', '↑' => '↑', '→' => '→', '↓' => '↓', '↔' => '↔', '↵' => '↵', '⇐' => '⇐', '⇑' => '⇑', '⇒' => '⇒', '⇓' => '⇓', '⇔' => '⇔', '∀' => '∀', '∂' => '∂', '∃' => '∃', '∅' => '∅', '∇' => '∇', '∈' => '∈', '∉' => '∉', '∋' => '∋', '∏' => '∏', '∑' => '∑', '−' => '−', '∗' => '∗', '√' => '√', '∝' => '∝', '∞' => '∞', '∠' => '∠', '∧' => '∧', '∨' => '∨', '∩' => '∩', '∪' => '∪', '∫' => '∫', '∴' => '∴', '∼' => '∼', '≅' => '≅', '≈' => '≈', '≠' => '≠', '≡' => '≡', '≤' => '≤', '≥' => '≥', '⊂' => '⊂', '⊃' => '⊃', '⊄' => '⊄', '⊆' => '⊆', '⊇' => '⊇', '⊕' => '⊕', '⊗' => '⊗', '⊥' => '⊥', '⋅' => '⋅', '⌈' => '⌈', '⌉' => '⌉', '⌊' => '⌊', '⌋' => '⌋', '⟨' => '〈', '⟩' => '〉', '◊' => '◊', '♠' => '♠', '♣' => '♣', '♥' => '♥', '♦' => '♦'));