Textpattern | PHP Cross Reference | Content Management Systems |
Description: Handles Atom 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 Atom feeds. 26 * 27 * @package XML 28 */ 29 30 /** 31 * @ignore 32 */ 33 34 define("t_texthtml", ' type="text/html"'); 35 36 /** 37 * @ignore 38 */ 39 40 define("t_text", ' type="text"'); 41 42 /** 43 * @ignore 44 */ 45 46 define("t_html", ' type="html"'); 47 48 /** 49 * @ignore 50 */ 51 52 define("t_xhtml", ' type="xhtml"'); 53 54 /** 55 * @ignore 56 */ 57 58 define('t_appxhtml', ' type="xhtml"'); 59 60 /** 61 * @ignore 62 */ 63 64 define("r_relalt", ' rel="alternate"'); 65 66 /** 67 * @ignore 68 */ 69 70 define("r_relself", ' rel="self"'); 71 72 /** 73 * Generates and outputs an Atom feed. 74 * 75 * This function can only be called once on a page. It outputs an Atom feed 76 * based on the requested URL parameters. Accepts HTTP GET parameters 'limit', 77 * 'area', 'section' and 'category'. 78 */ 79 80 function atom() 81 { 82 global $prefs, $txp_sections; 83 set_error_handler('feedErrorHandler'); 84 ob_clean(); 85 extract($prefs); 86 87 extract(doSlash(gpsa(array('limit', 'area')))); 88 89 // Build filter criteria from a comma-separated list of sections 90 // and categories. 91 $feed_filter_limit = get_pref('feed_filter_limit', 10); 92 $section = gps('section'); 93 $category = gps('category'); 94 95 if (!is_scalar($section) || !is_scalar($category)) { 96 txp_die('Not Found', 404); 97 } 98 99 $section = ($section ? array_slice(do_list_unique($section), 0, $feed_filter_limit) : array()); 100 $category = ($category ? array_slice(do_list_unique($category), 0, $feed_filter_limit) : array()); 101 $st = array(); 102 103 foreach ($section as $s) { 104 $st[] = fetch_section_title($s); 105 } 106 107 $ct = array(); 108 109 foreach ($category as $c) { 110 $ct[] = fetch_category_title($c); 111 } 112 113 $sitename .= ($section) ? ' - '.join(' - ', $st) : ''; 114 $sitename .= ($category) ? ' - '.join(' - ', $ct) : ''; 115 116 $pub = safe_row("RealName, email", 'txp_users', "privs = 1"); 117 118 // Feed header. 119 $out[] = tag(htmlspecialchars($sitename), 'title', t_text); 120 $out[] = tag(htmlspecialchars($site_slogan), 'subtitle', t_text); 121 $out[] = '<link'.r_relself.' href="'.pagelinkurl(array( 122 'atom' => 1, 123 'area' => $area, 124 'section' => $section, 125 'category' => $category, 126 'limit' => $limit, 127 )).'" />'; 128 $out[] = '<link'.r_relalt.t_texthtml.' href="'.hu.'" />'; 129 130 // Atom feeds with mail or domain name. 131 $dn = explode('/', $siteurl); 132 $mail_or_domain = ($use_mail_on_feeds_id) ? eE($blog_mail_uid) : $dn[0]; 133 $out[] = tag('tag:'.$mail_or_domain.','.$blog_time_uid.':'.$blog_uid.(($section) ? '/'.join(',', $section) : '').(($category) ? '/'.join(',', $category) : ''), 'id'); 134 135 $out[] = tag('Textpattern', 'generator', ' uri="https://textpattern.com/" version="'.$version.'"'); 136 $out[] = tag(safe_strftime("w3cdtf", strtotime($lastmod)), 'updated'); 137 138 $auth[] = tag($pub['RealName'], 'name'); 139 $auth[] = ($include_email_atom) ? tag(eE($pub['email']), 'email') : ''; 140 $auth[] = tag(hu, 'uri'); 141 142 $out[] = tag(n.t.t.join(n.t.t, $auth).n.t, 'author'); 143 $out[] = callback_event('atom_head'); 144 145 // Feed items. 146 $articles = array(); 147 $dates = array(); 148 $section = doSlash($section); 149 $category = doSlash($category); 150 $limit = ($limit) ? $limit : $rss_how_many; 151 $limit = intval(min($limit, max(100, $rss_how_many))); 152 153 if (!$area or $area == 'article') { 154 $sfilter = (!empty($section)) ? "AND Section IN ('".join("','", $section)."')" : ''; 155 $cfilter = (!empty($category)) ? "AND (Category1 IN ('".join("','", $category)."') OR Category2 IN ('".join("','", $category)."'))" : ''; 156 157 $query = array($sfilter, $cfilter); 158 159 $rs = array_filter(array_column($txp_sections, 'in_rss', 'name')); 160 161 if ($rs) { 162 $query[] = 'AND Section IN('.join(',', quote_list(array_keys($rs))).')'; 163 } 164 165 if ($atts = callback_event('feed_filter')) { 166 is_array($atts) or $atts = splat(trim($atts)); 167 } else { 168 $atts = array(); 169 } 170 171 $atts = filterAtts($atts, true); 172 $where = $atts['*'].' '.join(' ', $query); 173 174 $rs = safe_rows_start( 175 "*, 176 ID AS thisid, 177 UNIX_TIMESTAMP(Posted) AS uPosted, 178 UNIX_TIMESTAMP(Expires) AS uExpires, 179 UNIX_TIMESTAMP(LastMod) AS uLastMod", 180 'textpattern', 181 $where." ORDER BY Posted DESC LIMIT $limit" 182 ); 183 184 if ($rs) { 185 while ($a = nextRow($rs)) { 186 // In case $GLOBALS['thisarticle'] is unset 187 global $thisarticle; 188 extract($a); 189 populateArticleData($a); 190 $cb = callback_event('atom_entry'); 191 $e = array(); 192 193 $a['posted'] = $uPosted; 194 $a['expires'] = $uExpires; 195 196 if ($show_comment_count_in_feed) { 197 $count = ($comments_count > 0) ? ' ['.$comments_count.']' : ''; 198 } else { 199 $count = ''; 200 } 201 202 $thisauthor = get_author_name($AuthorID); 203 204 $e['thisauthor'] = tag(n.t.t.t.tag(htmlspecialchars($thisauthor), 'name').n.t.t, 'author'); 205 206 $e['issued'] = tag(safe_strftime('w3cdtf', $uPosted), 'published'); 207 $e['modified'] = tag(safe_strftime('w3cdtf', $uLastMod), 'updated'); 208 209 $escaped_title = htmlspecialchars($thisarticle['title']); 210 $e['title'] = tag($escaped_title.$count, 'title', t_html); 211 212 $permlink = permlinkurl($a); 213 $e['link'] = '<link'.r_relalt.t_texthtml.' href="'.$permlink.'" />'; 214 215 $e['id'] = tag('tag:'.$mail_or_domain.','.$feed_time.':'.$blog_uid.'/'.$uid, 'id'); 216 217 $e['category1'] = (trim($Category1) ? '<category term="'.htmlspecialchars($Category1).'" />' : ''); 218 $e['category2'] = (trim($Category2) ? '<category term="'.htmlspecialchars($Category2).'" />' : ''); 219 220 $summary = trim(replace_relative_urls(parse($thisarticle['excerpt']), $permlink)); 221 $content = trim(replace_relative_urls(parse($thisarticle['body']), $permlink)); 222 223 if ($syndicate_body_or_excerpt) { 224 // Short feed: use body as summary if there's no excerpt. 225 if (!trim($summary)) { 226 $summary = $content; 227 } 228 229 $content = ''; 230 } 231 232 if (trim($content)) { 233 $e['content'] = tag(escape_cdata($content), 'content', t_html); 234 } 235 236 if (trim($summary)) { 237 $e['summary'] = tag(escape_cdata($summary), 'summary', t_html); 238 } 239 240 $articles[$ID] = tag(n.t.t.join(n.t.t, $e).n.t.$cb, 'entry'); 241 242 $dates[$ID] = $uLastMod; 243 } 244 } 245 } elseif ($area == 'link') { 246 $cfilter = ($category) ? "category IN ('".join("','", $category)."')" : '1'; 247 248 $rs = safe_rows_start("*, UNIX_TIMESTAMP(date) AS uDate", 'txp_link', "$cfilter ORDER BY date DESC, id DESC LIMIT $limit"); 249 250 if ($rs) { 251 while ($a = nextRow($rs)) { 252 extract($a); 253 $e = array(); 254 255 $e['title'] = tag(htmlspecialchars($linkname), 'title', t_html); 256 $e['content'] = tag(escape_cdata($description), 'content', t_html); 257 258 $url = (preg_replace("/^\/(.*)/", "https?://$siteurl/$1", $url)); 259 $url = preg_replace("/&((?U).*)=/", "&\\1=", $url); 260 $e['link'] = '<link'.r_relalt.t_texthtml.' href="'.$url.'" />'; 261 262 $e['issued'] = tag(safe_strftime('w3cdtf', $uDate), 'published'); 263 $e['modified'] = tag(safe_strftime('w3cdtf', $uDate), 'updated'); 264 $e['id'] = tag('tag:'.$mail_or_domain.','.safe_strftime('%Y-%m-%d', $uDate).':'.$blog_uid.'/'.$id, 'id'); 265 266 $articles[$id] = tag(n.t.t.join(n.t.t, $e).n.t, 'entry'); 267 268 $dates[$id] = $uDate; 269 } 270 } 271 } 272 273 if (!$articles) { 274 if ($section) { 275 if (safe_field("name", 'txp_section', "name IN ('".join("','", $section)."')") == false) { 276 txp_die(gTxt('404_not_found'), '404'); 277 } 278 } elseif ($category) { 279 switch ($area) { 280 case 'link': 281 if (safe_field("id", 'txp_category', "name = '$category' AND type = 'link'") == false) { 282 txp_die(gTxt('404_not_found'), '404'); 283 } 284 285 break; 286 case 'article': 287 default: 288 if (safe_field("id", 'txp_category', "name IN ('".join("','", $category)."') AND type = 'article'") == false) { 289 txp_die(gTxt('404_not_found'), '404'); 290 } 291 292 break; 293 } 294 } 295 } else { 296 header('Vary: A-IM, If-None-Match, If-Modified-Since'); 297 298 handle_lastmod(max($dates)); 299 300 // Get timestamp from request caching headers 301 if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { 302 $hims = $_SERVER['HTTP_IF_MODIFIED_SINCE']; 303 $imsd = ($hims) ? strtotime($hims) : 0; 304 } elseif (isset($_SERVER['HTTP_IF_NONE_MATCH'])) { 305 $hinm = trim(trim($_SERVER['HTTP_IF_NONE_MATCH']), '"'); 306 $hinm_apache_gzip_workaround = explode('-gzip', $hinm); 307 $hinm_apache_gzip_workaround = $hinm_apache_gzip_workaround[0]; 308 $inmd = ($hinm) ? base_convert($hinm_apache_gzip_workaround, 32, 10) : 0; 309 } 310 311 if (isset($imsd) || isset($inmd)) { 312 $clfd = max(intval($imsd), intval($inmd)); 313 } 314 315 $cutarticles = false; 316 317 if (isset($_SERVER["HTTP_A_IM"]) && 318 strpos($_SERVER["HTTP_A_IM"], "feed") && 319 isset($clfd) && $clfd > 0) { 320 321 // Remove articles with timestamps older than the request timestamp 322 foreach ($articles as $id => $entry) { 323 if ($dates[$id] <= $clfd) { 324 unset($articles[$id]); 325 $cutarticles = true; 326 } 327 } 328 } 329 330 // Indicate that instance manipulation was applied 331 if ($cutarticles) { 332 header("HTTP/1.1 226 IM Used"); 333 header("Cache-Control: IM", false); 334 header("IM: feed", false); 335 } 336 } 337 338 $out = array_merge($out, $articles); 339 $xmlns = ''; 340 341 $feeds_namespaces = parse_ini_string(get_pref('feeds_namespaces')); 342 is_array($feeds_namespaces) or $feeds_namespaces = array(); 343 344 foreach ($feeds_namespaces as $ns => $url) { 345 $xmlns .= ' xmlns:'.$ns.'="'.$url.'"'; 346 } 347 348 header('Content-Type: application/atom+xml; charset=utf-8'); 349 350 return 351 '<?xml version="1.0" encoding="UTF-8"?>'.n. 352 '<feed xml:lang="'.txpspecialchars($language).'" xmlns="http://www.w3.org/2005/Atom"'.$xmlns.'>'.n.t. 353 join(n.t, $out).n. 354 '</feed>'; 355 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
title