Textpattern | PHP Cross Reference | Content Management Systems |
Description: Handles RSS feeds.
1 <?php 2 3 /* 4 * Textpattern Content Management System 5 * https://textpattern.com/ 6 * 7 * Copyright (C) 2020 The Textpattern Development Team 8 * 9 * This file is part of Textpattern. 10 * 11 * Textpattern is free software; you can redistribute it and/or 12 * modify it under the terms of the GNU General Public License 13 * as published by the Free Software Foundation, version 2. 14 * 15 * Textpattern is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 * GNU General Public License for more details. 19 * 20 * You should have received a copy of the GNU General Public License 21 * along with Textpattern. If not, see <https://www.gnu.org/licenses/>. 22 */ 23 24 /** 25 * Handles RSS feeds. 26 * 27 * @package XML 28 */ 29 30 /** 31 * Generates and returns an RSS feed. 32 * 33 * This function can only be called once on a page. It send HTTP 34 * headers and returns an RSS feed based on the requested URL parameters. 35 * Accepts HTTP GET parameters 'limit', 'area', 'section' and 'category'. 36 * 37 * @return string XML 38 */ 39 40 function rss() 41 { 42 global $prefs, $txp_sections; 43 set_error_handler('feedErrorHandler'); 44 ob_clean(); 45 extract($prefs); 46 47 extract(doSlash(gpsa(array('limit', 'area')))); 48 49 // Build filter criteria from a comma-separated list of sections 50 // and categories. 51 $feed_filter_limit = get_pref('feed_filter_limit', 10); 52 $section = gps('section'); 53 $category = gps('category'); 54 55 if (!is_scalar($section) || !is_scalar($category)) { 56 txp_die('Not Found', 404); 57 } 58 59 $section = ($section ? array_slice(do_list_unique($section), 0, $feed_filter_limit) : array()); 60 $category = ($category ? array_slice(do_list_unique($category), 0, $feed_filter_limit) : array()); 61 $st = array(); 62 63 foreach ($section as $s) { 64 $st[] = fetch_section_title($s); 65 } 66 67 $ct = array(); 68 69 foreach ($category as $c) { 70 $ct[] = fetch_category_title($c); 71 } 72 73 $sitename .= ($section) ? ' - '.join(' - ', $st) : ''; 74 $sitename .= ($category) ? ' - '.join(' - ', $ct) : ''; 75 $dn = explode('/', $siteurl); 76 $mail_or_domain = ($use_mail_on_feeds_id) ? eE($blog_mail_uid) : $dn[0]; 77 78 // Feed header. 79 $out[] = tag('https://textpattern.com/?v='.$version, 'generator'); 80 $out[] = tag(doSpecial($sitename), 'title'); 81 $out[] = tag(hu, 'link'); 82 $out[] = '<atom:link href="'.pagelinkurl(array( 83 'rss' => 1, 84 'area' => $area, 85 'section' => $section, 86 'category' => $category, 87 'limit' => $limit, 88 )).'" rel="self" type="application/rss+xml" />'; 89 $out[] = tag(doSpecial($site_slogan), 'description'); 90 $out[] = tag(safe_strftime('rfc822', strtotime($lastmod)), 'pubDate'); 91 $out[] = callback_event('rss_head'); 92 93 // Feed items. 94 $articles = array(); 95 $dates = array(); 96 $section = doSlash($section); 97 $category = doSlash($category); 98 $limit = ($limit) ? $limit : $rss_how_many; 99 $limit = intval(min($limit, max(100, $rss_how_many))); 100 101 if (!$area or $area == 'article') { 102 $sfilter = (!empty($section)) ? "AND Section IN ('".join("','", $section)."')" : ''; 103 $cfilter = (!empty($category)) ? "AND (Category1 IN ('".join("','", $category)."') OR Category2 IN ('".join("','", $category)."'))" : ''; 104 105 $query = array($sfilter, $cfilter); 106 107 $rs = array_filter(array_column($txp_sections, 'in_rss', 'name')); 108 109 if ($rs) { 110 $query[] = 'AND Section IN('.join(',', quote_list(array_keys($rs))).')'; 111 } 112 113 if ($atts = callback_event('feed_filter')) { 114 is_array($atts) or $atts = splat(trim($atts)); 115 } else { 116 $atts = array(); 117 } 118 119 $atts = filterAtts($atts, true); 120 $where = $atts['*'].' '.join(' ', $query); 121 122 $rs = safe_rows_start( 123 "*, 124 ID AS thisid, 125 UNIX_TIMESTAMP(Posted) AS uPosted, 126 UNIX_TIMESTAMP(Expires) AS uExpires, 127 UNIX_TIMESTAMP(LastMod) AS uLastMod", 128 'textpattern', 129 $where." ORDER BY Posted DESC LIMIT $limit" 130 ); 131 132 if ($rs) { 133 while ($a = nextRow($rs)) { 134 // In case $GLOBALS['thisarticle'] is unset 135 global $thisarticle; 136 extract($a); 137 populateArticleData($a); 138 139 $cb = callback_event('rss_entry'); 140 141 $a['posted'] = $uPosted; 142 $a['expires'] = $uExpires; 143 144 if ($show_comment_count_in_feed) { 145 $count = ($comments_count > 0) ? ' ['.$comments_count.']' : ''; 146 } else { 147 $count = ''; 148 } 149 150 $permlink = permlinkurl($thisarticle); 151 $summary = trim(parse($thisarticle['excerpt']), $permlink); 152 $content = ''; 153 154 if ($syndicate_body_or_excerpt) { 155 // Short feed: use body as summary if there's no excerpt. 156 if ($summary === '') { 157 $summary = trim(parse($thisarticle['body'])); 158 } 159 } else { 160 $content = trim(parse($thisarticle['body'])); 161 } 162 163 $Title = escape_title(preg_replace("/&(?![#a-z0-9]+;)/i", "&", html_entity_decode(strip_tags($thisarticle['title']), ENT_QUOTES, 'UTF-8'))).$count; 164 165 $thisauthor = get_author_name($AuthorID); 166 167 $item = 168 n.t.t.tag($Title, 'title'). 169 ($summary !== '' ? n.t.t.tag(escape_cdata($summary), 'description') : ''). 170 ($content !== '' ? n.t.t.tag(escape_cdata($content).n, 'content:encoded') : ''). 171 n.t.t.tag($permlink, 'link'). 172 n.t.t.tag(safe_strftime('rfc822', $a['posted']), 'pubDate'). 173 n.t.t.tag(htmlspecialchars($thisauthor), 'dc:creator'). 174 n.t.t.tag('tag:'.$mail_or_domain.','.$feed_time.':'.$blog_uid.'/'.$uid, 'guid', ' isPermaLink="false"').n. 175 $cb; 176 177 $articles[$ID] = tag(replace_relative_urls($item, $permlink).t, 'item'); 178 179 $dates[$ID] = $uLastMod; 180 } 181 } 182 } elseif ($area == 'link') { 183 $cfilter = ($category) ? "category IN ('".join("','", $category)."')" : '1'; 184 185 $rs = safe_rows_start("*, UNIX_TIMESTAMP(date) AS uDate", 'txp_link', "$cfilter ORDER BY date DESC, id DESC LIMIT $limit"); 186 187 if ($rs) { 188 while ($a = nextRow($rs)) { 189 extract($a); 190 $item = 191 n.t.t.tag(doSpecial($linkname), 'title'). 192 (trim($description) ? n.t.t.tag(doSpecial($description), 'description') : ''). 193 n.t.t.tag(doSpecial($url), 'link'). 194 n.t.t.tag(safe_strftime('rfc822', $uDate), 'pubDate').n; 195 $articles[$id] = tag($item.t, 'item'); 196 197 $dates[$id] = $uDate; 198 } 199 } 200 } 201 202 if (!$articles) { 203 if ($section) { 204 if (safe_field("name", 'txp_section', "name IN ('".join("','", $section)."')") == false) { 205 txp_die(gTxt('404_not_found'), '404'); 206 } 207 } elseif ($category) { 208 switch ($area) { 209 case 'link': 210 if (safe_field("id", 'txp_category', "name = '$category' AND type = 'link'") == false) { 211 txp_die(gTxt('404_not_found'), '404'); 212 } 213 214 break; 215 case 'article': 216 default: 217 if (safe_field("id", 'txp_category', "name IN ('".join("','", $category)."') AND type = 'article'") == false) { 218 txp_die(gTxt('404_not_found'), '404'); 219 } 220 221 break; 222 } 223 } 224 } else { 225 header('Vary: A-IM, If-None-Match, If-Modified-Since'); 226 227 handle_lastmod(max($dates)); 228 229 // Get timestamp from request caching headers 230 if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { 231 $hims = $_SERVER['HTTP_IF_MODIFIED_SINCE']; 232 $imsd = ($hims) ? strtotime($hims) : 0; 233 } elseif (isset($_SERVER['HTTP_IF_NONE_MATCH'])) { 234 $hinm = trim(trim($_SERVER['HTTP_IF_NONE_MATCH']), '"'); 235 $hinm_apache_gzip_workaround = explode('-gzip', $hinm); 236 $hinm_apache_gzip_workaround = $hinm_apache_gzip_workaround[0]; 237 $inmd = ($hinm) ? base_convert($hinm_apache_gzip_workaround, 32, 10) : 0; 238 } 239 240 if (isset($imsd) || isset($inmd)) { 241 $clfd = max(intval($imsd), intval($inmd)); 242 } 243 244 $cutarticles = false; 245 246 if (isset($_SERVER["HTTP_A_IM"]) && 247 strpos($_SERVER["HTTP_A_IM"], "feed") && 248 isset($clfd) && $clfd > 0) { 249 250 // Remove articles with timestamps older than the request timestamp 251 foreach ($articles as $id => $entry) { 252 if ($dates[$id] <= $clfd) { 253 unset($articles[$id]); 254 $cutarticles = true; 255 } 256 } 257 } 258 259 // Indicate that instance manipulation was applied 260 if ($cutarticles) { 261 header("HTTP/1.1 226 IM Used"); 262 header("Cache-Control: IM", false); 263 header("IM: feed", false); 264 } 265 } 266 267 $out = array_merge($out, $articles); 268 $xmlns = ''; 269 270 $feeds_namespaces = parse_ini_string(get_pref('feeds_namespaces')); 271 is_array($feeds_namespaces) or $feeds_namespaces = array(); 272 $feeds_namespaces += array( 273 'dc' => 'http://purl.org/dc/elements/1.1/', 274 'content' => 'http://purl.org/rss/1.0/modules/content/', 275 'atom' => 'http://www.w3.org/2005/Atom' 276 ); 277 278 foreach ($feeds_namespaces as $ns => $url) { 279 $xmlns .= ' xmlns:'.$ns.'="'.$url.'"'; 280 } 281 282 header('Content-Type: application/rss+xml; charset=utf-8'); 283 284 return 285 '<?xml version="1.0" encoding="UTF-8"?>'.n. 286 '<rss version="2.0"'.$xmlns.'>'.n. 287 tag(n.t.join(n.t, $out).n, 'channel').n. 288 '</rss>'; 289 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
title