Textpattern PHP Cross Reference Content Management Systems

Source: /textpattern/publish/atom.php - 355 lines - 11733 bytes - Summary - Text - Print

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).*)=/", "&amp;\\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

title

Description

title

Description

title

title

Body