Textpattern | PHP Cross Reference | Content Management Systems |
Description: Handles Atom 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 Atom feeds. 27 * 28 * @package XML 29 */ 30 31 /** 32 * @ignore 33 */ 34 35 define("t_texthtml", ' type="text/html"'); 36 37 /** 38 * @ignore 39 */ 40 41 define("t_text", ' type="text"'); 42 43 /** 44 * @ignore 45 */ 46 47 define("t_html", ' type="html"'); 48 49 /** 50 * @ignore 51 */ 52 53 define("t_xhtml", ' type="xhtml"'); 54 55 /** 56 * @ignore 57 */ 58 59 define('t_appxhtml', ' type="xhtml"'); 60 61 /** 62 * @ignore 63 */ 64 65 define("r_relalt", ' rel="alternate"'); 66 67 /** 68 * @ignore 69 */ 70 71 define("r_relself", ' rel="self"'); 72 73 /** 74 * Generates and outputs an Atom feed. 75 * 76 * This function can only be called once on a page. It outputs an Atom feed 77 * based on the requested URL parameters. Accepts HTTP GET parameters 'limit', 78 * 'area', 'section' and 'category'. 79 */ 80 81 function atom() 82 { 83 global $prefs; 84 set_error_handler('feedErrorHandler'); 85 ob_clean(); 86 extract($prefs); 87 88 $last = fetch("UNIX_TIMESTAMP(val)", 'txp_prefs', 'name', 'lastmod'); 89 90 extract(doSlash(gpsa(array( 91 'limit', 92 'area', 93 )))); 94 95 // Build filter criteria from a comma-separated list of sections 96 // and categories. 97 $feed_filter_limit = get_pref('feed_filter_limit', 10); 98 $section = gps('section'); 99 $category = gps('category'); 100 101 if (!is_scalar($section) || !is_scalar($category)) { 102 txp_die('Not Found', 404); 103 } 104 105 $section = ($section ? array_slice(do_list_unique($section), 0, $feed_filter_limit) : array()); 106 $category = ($category ? array_slice(do_list_unique($category), 0, $feed_filter_limit) : array()); 107 $st = array(); 108 109 foreach ($section as $s) { 110 $st[] = fetch_section_title($s); 111 } 112 113 $ct = array(); 114 115 foreach ($category as $c) { 116 $ct[] = fetch_category_title($c); 117 } 118 119 $sitename .= ($section) ? ' - '.join(' - ', $st) : ''; 120 $sitename .= ($category) ? ' - '.join(' - ', $ct) : ''; 121 122 $pub = safe_row("RealName, email", 'txp_users', "privs = 1"); 123 124 // Feed header. 125 $out[] = tag(htmlspecialchars($sitename), 'title', t_text); 126 $out[] = tag(htmlspecialchars($site_slogan), 'subtitle', t_text); 127 $out[] = '<link'.r_relself.' href="'.pagelinkurl(array( 128 'atom' => 1, 129 'area' => $area, 130 'section' => $section, 131 'category' => $category, 132 'limit' => $limit, 133 )).'" />'; 134 $out[] = '<link'.r_relalt.t_texthtml.' href="'.hu.'" />'; 135 136 // Atom feeds with mail or domain name. 137 $dn = explode('/', $siteurl); 138 $mail_or_domain = ($use_mail_on_feeds_id) ? eE($blog_mail_uid) : $dn[0]; 139 $out[] = tag('tag:'.$mail_or_domain.','.$blog_time_uid.':'.$blog_uid.(($section) ? '/'.join(',', $section) : '').(($category) ? '/'.join(',', $category) : ''), 'id'); 140 141 $out[] = tag('Textpattern', 'generator', ' uri="http://textpattern.com/" version="'.$version.'"'); 142 $out[] = tag(safe_strftime("w3cdtf", $last), 'updated'); 143 144 $auth[] = tag($pub['RealName'], 'name'); 145 $auth[] = ($include_email_atom) ? tag(eE($pub['email']), 'email') : ''; 146 $auth[] = tag(hu, 'uri'); 147 148 $out[] = tag(n.t.t.join(n.t.t, $auth).n, 'author'); 149 $out[] = callback_event('atom_head'); 150 151 // Feed items. 152 $articles = array(); 153 $section = doSlash($section); 154 $category = doSlash($category); 155 156 if (!$area or $area == 'article') { 157 $sfilter = (!empty($section)) ? "AND Section IN ('".join("','", $section)."')" : ''; 158 $cfilter = (!empty($category)) ? "AND (Category1 IN ('".join("','", $category)."') OR Category2 IN ('".join("','", $category)."'))" : ''; 159 $limit = ($limit) ? $limit : $rss_how_many; 160 $limit = intval(min($limit, max(100, $rss_how_many))); 161 162 $frs = safe_column("name", 'txp_section', "in_rss != '1'"); 163 164 $query = array(); 165 166 foreach ($frs as $f) { 167 $query[] = "AND Section != '".doSlash($f)."'"; 168 } 169 170 $query[] = $sfilter; 171 $query[] = $cfilter; 172 173 $expired = ($publish_expired_articles) ? " " : " AND (".now('expires')." <= Expires OR Expires IS NULL) "; 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 "Status = 4 AND Posted <= ".now('posted').$expired.join(' ', $query). 182 "ORDER BY Posted DESC LIMIT $limit" 183 ); 184 185 if ($rs) { 186 while ($a = nextRow($rs)) { 187 // In case $GLOBALS['thisarticle'] is unset 188 global $thisarticle; 189 extract($a); 190 populateArticleData($a); 191 $cb = callback_event('atom_entry'); 192 $e = array(); 193 194 $a['posted'] = $uPosted; 195 $a['expires'] = $uExpires; 196 197 if ($show_comment_count_in_feed) { 198 $count = ($comments_count > 0) ? ' ['.$comments_count.']' : ''; 199 } else { 200 $count = ''; 201 } 202 203 $thisauthor = get_author_name($AuthorID); 204 205 $e['thisauthor'] = tag(n.t.t.t.tag(htmlspecialchars($thisauthor), 'name').n.t.t, 'author'); 206 207 $e['issued'] = tag(safe_strftime('w3cdtf', $uPosted), 'published'); 208 $e['modified'] = tag(safe_strftime('w3cdtf', $uLastMod), 'updated'); 209 210 $escaped_title = htmlspecialchars($Title); 211 $e['title'] = tag($escaped_title.$count, 'title', t_html); 212 213 $permlink = permlinkurl($a); 214 $e['link'] = '<link'.r_relalt.t_texthtml.' href="'.$permlink.'" />'; 215 216 $e['id'] = tag('tag:'.$mail_or_domain.','.$feed_time.':'.$blog_uid.'/'.$uid, 'id'); 217 218 $e['category1'] = (trim($Category1) ? '<category term="'.htmlspecialchars($Category1).'" />' : ''); 219 $e['category2'] = (trim($Category2) ? '<category term="'.htmlspecialchars($Category2).'" />' : ''); 220 221 $summary = trim(replace_relative_urls(parse($thisarticle['excerpt']), $permlink)); 222 $content = trim(replace_relative_urls(parse($thisarticle['body']), $permlink)); 223 224 if ($syndicate_body_or_excerpt) { 225 // Short feed: use body as summary if there's no excerpt. 226 if (!trim($summary)) { 227 $summary = $content; 228 } 229 $content = ''; 230 } 231 232 if (trim($content)) { 233 $e['content'] = tag(n.escape_cdata($content).n, 'content', t_html); 234 } 235 236 if (trim($summary)) { 237 $e['summary'] = tag(n.escape_cdata($summary).n, 'summary', t_html); 238 } 239 240 $articles[$ID] = tag(n.t.t.join(n.t.t, $e).n.$cb, 'entry'); 241 242 $etags[$ID] = strtoupper(dechex(crc32($articles[$ID]))); 243 $dates[$ID] = $uLastMod; 244 } 245 } 246 } elseif ($area == 'link') { 247 $cfilter = ($category) ? "category in ('".join("','", $category)."')" : '1'; 248 $limit = ($limit) ? $limit : $rss_how_many; 249 $limit = intval(min($limit, max(100, $rss_how_many))); 250 251 $rs = safe_rows_start("*", 'txp_link', "$cfilter ORDER BY date DESC, id DESC LIMIT $limit"); 252 253 if ($rs) { 254 while ($a = nextRow($rs)) { 255 extract($a); 256 257 $e['title'] = tag(htmlspecialchars($linkname), 'title', t_html); 258 $e['content'] = tag(n.htmlspecialchars($description).n, 'content', t_html); 259 260 $url = (preg_replace("/^\/(.*)/", "https?://$siteurl/$1", $url)); 261 $url = preg_replace("/&((?U).*)=/", "&\\1=", $url); 262 $e['link'] = '<link'.r_relalt.t_texthtml.' href="'.$url.'" />'; 263 264 $e['issued'] = tag(safe_strftime('w3cdtf', strtotime($date)), 'published'); 265 $e['modified'] = tag(gmdate('Y-m-d\TH:i:s\Z', strtotime($date)), 'updated'); 266 $e['id'] = tag('tag:'.$mail_or_domain.','.safe_strftime('%Y-%m-%d', strtotime($date)).':'.$blog_uid.'/'.$id, 'id'); 267 268 $articles[$id] = tag(n.t.t.join(n.t.t, $e).n, 'entry'); 269 270 $etags[$id] = strtoupper(dechex(crc32($articles[$id]))); 271 $dates[$id] = $date; 272 } 273 } 274 } 275 276 if (!$articles) { 277 if ($section) { 278 if (safe_field("name", 'txp_section', "name IN ('".join("','", $section)."')") == false) { 279 txp_die(gTxt('404_not_found'), '404'); 280 } 281 } elseif ($category) { 282 switch ($area) { 283 case 'link': 284 if (safe_field("id", 'txp_category', "name = '$category' AND type = 'link'") == false) { 285 txp_die(gTxt('404_not_found'), '404'); 286 } 287 break; 288 case 'article': 289 default: 290 if (safe_field("id", 'txp_category', "name IN ('".join("','", $category)."') AND type = 'article'") == false) { 291 txp_die(gTxt('404_not_found'), '404'); 292 } 293 break; 294 } 295 } 296 } else { 297 handle_lastmod(); 298 $hims = serverset('HTTP_IF_MODIFIED_SINCE'); 299 $imsd = ($hims) ? strtotime($hims) : 0; 300 301 if (is_callable('apache_request_headers')) { 302 $headers = apache_request_headers(); 303 304 if (isset($headers["A-IM"])) { 305 $canaim = strpos($headers["A-IM"], "feed"); 306 } else { 307 $canaim = false; 308 } 309 } else { 310 $canaim = false; 311 } 312 313 $hinm = stripslashes(serverset('HTTP_IF_NONE_MATCH')); 314 315 $cutarticles = false; 316 317 if ($canaim !== false) { 318 foreach ($articles as $id => $thing) { 319 if (strpos($hinm, $etags[$id])) { 320 unset($articles[$id]); 321 $cutarticles = true; 322 $cut_etag = true; 323 } 324 325 if ($dates[$id] < $imsd) { 326 unset($articles[$id]); 327 $cutarticles = true; 328 $cut_time = true; 329 } 330 } 331 } 332 333 if (isset($cut_etag) && isset($cut_time)) { 334 header("Vary: If-None-Match, If-Modified-Since"); 335 } elseif (isset($cut_etag)) { 336 header("Vary: If-None-Match"); 337 } elseif (isset($cut_time)) { 338 header("Vary: If-Modified-Since"); 339 } 340 341 $etag = @join("-", $etags); 342 343 if (strstr($hinm, $etag)) { 344 txp_status_header('304 Not Modified'); 345 exit(0); 346 } 347 348 if ($etag) { 349 header('ETag: "'.$etag.'"'); 350 } 351 352 if ($cutarticles) { 353 // header("HTTP/1.1 226 IM Used"); 354 // This should be used as opposed to 200, but Apache doesn't like it. 355 header("Cache-Control: no-store, im"); 356 header("IM: feed"); 357 } 358 } 359 360 $out = array_merge($out, $articles); 361 362 header('Content-type: application/atom+xml; charset=utf-8'); 363 364 return chr(60).'?xml version="1.0" encoding="UTF-8"?'.chr(62).n. 365 '<feed xml:lang="'.txpspecialchars($language).'" xmlns="http://www.w3.org/2005/Atom">'.join(n, $out).'</feed>'; 366 } 367 368 /** 369 * Converts HTML entieties to UTF-8 characters. 370 * 371 * This is included only for backwards compatibility with older plugins. 372 * 373 * @param string $toUnicode 374 * @return string 375 * @deprecated in 4.0.4 376 */ 377 378 function safe_hed($toUnicode) 379 { 380 if (version_compare(phpversion(), "5.0.0", ">=")) { 381 $str = html_entity_decode($toUnicode, ENT_QUOTES, "UTF-8"); 382 } else { 383 $trans_tbl = get_html_translation_table(HTML_ENTITIES); 384 foreach ($trans_tbl as $k => $v) { 385 $ttr[$v] = utf8_encode($k); 386 } 387 $str = strtr($toUnicode, $ttr); 388 } 389 390 return $str; 391 } 392 393 /** 394 * Sanitises a string for use in a feed. 395 * 396 * Tries to resolve relative URLs and encode unescaped characters. 397 * 398 * This is included only for backwards compatibility with older plugins. 399 * 400 * @param string $toFeed 401 * @param string $permalink 402 * @return string 403 * @deprecated in 4.0.4 404 */ 405 406 function fixup_for_feed($toFeed, $permalink) 407 { 408 // Fix relative urls. 409 $txt = str_replace('href="/', 'href="'.hu.'/', $toFeed); 410 $txt = preg_replace("/href=\\\"#(.*)\"/", "href=\"".$permalink."#\\1\"", $txt); 411 // This was removed as entities shouldn't be stripped in Atom feeds when the 412 // content type is HTML. Leaving it commented out as a reminder. 413 //$txt = safe_hed($txt); 414 415 // Encode and entify. 416 $txt = preg_replace(array('/</', '/>/', "/'/", '/"/'), array('<', '>', ''', '"'), $txt); 417 $txt = preg_replace("/&(?![#0-9]+;)/i", '&', $txt); 418 419 return $txt; 420 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
title