Textpattern PHP Cross Reference Content Management Systems

Source: /textpattern/publish/rss.php - 289 lines - 10196 bytes - Summary - Text - Print

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", "&amp;", 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

title

Description

title

Description

title

title

Body