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