Textpattern PHP Cross Reference Content Management Systems

Source: /textpattern/lib/txplib_publish.php - 777 lines - 23174 bytes - Summary - Text - Print

Description: Tools for page routing and handling article data.

   1  <?php
   2  
   3  /*
   4   * Textpattern Content Management System
   5   * http://textpattern.com
   6   *
   7   * Copyright (C) 2016 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 <http://www.gnu.org/licenses/>.
  22   */
  23  
  24  /**
  25   * Tools for page routing and handling article data.
  26   *
  27   * @since   4.5.0
  28   * @package Routing
  29   */
  30  
  31  /**
  32   * Build a query qualifier to remove non-frontpage articles from the result set.
  33   *
  34   * @return string An SQL qualifier for a query's 'WHERE' part
  35   */
  36  
  37  function filterFrontPage()
  38  {
  39      static $filterFrontPage;
  40  
  41      if (isset($filterFrontPage)) {
  42          return $filterFrontPage;
  43      }
  44  
  45      $filterFrontPage = false;
  46  
  47      $rs = safe_column("name", 'txp_section', "on_frontpage != '1'");
  48  
  49      if ($rs) {
  50          $filters = array();
  51  
  52          foreach ($rs as $name) {
  53              $filters[] = " and Section != '".doSlash($name)."'";
  54          }
  55  
  56          $filterFrontPage = join('', $filters);
  57      }
  58  
  59      return $filterFrontPage;
  60  }
  61  
  62  /**
  63   * Populates the current article data.
  64   *
  65   * Fills members of $thisarticle global from a database row.
  66   *
  67   * Keeps all article tag-related values in one place, in order to do easy
  68   * bugfixing and ease the addition of new article tags.
  69   *
  70   * @param array $rs An article as an assocative array
  71   * @example
  72   * if ($rs = safe_rows_start("*,
  73   *     UNIX_TIMESTAMP(Posted) AS uPosted,
  74   *     UNIX_TIMESTAMP(Expires) AS uExpires,
  75   *     UNIX_TIMESTAMP(LastMod) AS uLastMod",
  76   *     'textpattern',
  77   *     "1 = 1"
  78   * ))
  79   * {
  80   *     global $thisarticle;
  81   *     while ($row = nextRow($rs))
  82   *     {
  83   *         populateArticleData($row);
  84   *         echo $thisarticle['title'];
  85   *     }
  86   * }
  87   */
  88  
  89  function populateArticleData($rs)
  90  {
  91      global $thisarticle, $trace;
  92  
  93      $trace->log("[Article: '{$rs['ID']}']");
  94  
  95      foreach (article_column_map() as $key => $column) {
  96          $thisarticle[$key] = $rs[$column];
  97      }
  98  }
  99  
 100  /**
 101   * Formats article info and populates the current article data.
 102   *
 103   * Fills members of $thisarticle global from a database row.
 104   *
 105   * Basically just converts an article's date values to UNIX timestamps.
 106   * Convenience for those who prefer doing conversion in application end instead
 107   * of in the SQL statement.
 108   *
 109   * @param array $rs An article as an assocative array
 110   * @example
 111   * article_format_info(
 112   *     safe_row('*', 'textpattern', 'Status = 4 LIMIT 1')
 113   * )
 114   */
 115  
 116  function article_format_info($rs)
 117  {
 118      $rs['uPosted']  = (($unix_ts = @strtotime($rs['Posted']))  !== false) ? $unix_ts : null;
 119      $rs['uLastMod'] = (($unix_ts = @strtotime($rs['LastMod'])) !== false) ? $unix_ts : null;
 120      $rs['uExpires'] = (($unix_ts = @strtotime($rs['Expires'])) !== false) ? $unix_ts : null;
 121      populateArticleData($rs);
 122  }
 123  
 124  /**
 125   * Maps 'textpattern' table's columns to article data values.
 126   *
 127   * This function returns an array of 'data-value' => 'column' pairs.
 128   *
 129   * @return array
 130   */
 131  
 132  function article_column_map()
 133  {
 134      $custom = getCustomFields();
 135      $custom_map = array();
 136  
 137      if ($custom) {
 138          foreach ($custom as $i => $name) {
 139              $custom_map[$name] = 'custom_'.$i;
 140          }
 141      }
 142  
 143      return array(
 144          'thisid' => 'ID',
 145          'posted' => 'uPosted',    // Calculated value!
 146          'expires' => 'uExpires',  // Calculated value!
 147          'modified' => 'uLastMod', // Calculated value!
 148          'annotate' => 'Annotate',
 149          'comments_invite' => 'AnnotateInvite',
 150          'authorid' => 'AuthorID',
 151          'title' => 'Title',
 152          'url_title' => 'url_title',
 153          'description' => 'description',
 154          'category1' => 'Category1',
 155          'category2' => 'Category2',
 156          'section' => 'Section',
 157          'keywords' => 'Keywords',
 158          'article_image' => 'Image',
 159          'comments_count' => 'comments_count',
 160          'body' => 'Body_html',
 161          'excerpt' => 'Excerpt_html',
 162          'override_form' => 'override_form',
 163          'status' => 'Status',
 164      ) + $custom_map;
 165  }
 166  
 167  /**
 168   * Find an adjacent article relative to a provided threshold level.
 169   *
 170   * @param  scalar $threshold      The value to compare against
 171   * @param  string $s              Optional section restriction
 172   * @param  string $type           Lesser or greater neighbour? Either '<' (previous) or '>' (next)
 173   * @param  array  $atts           Attribute of article at threshold
 174   * @param  string $threshold_type 'cooked': Use $threshold as SQL clause; 'raw': Use $threshold as an escapable scalar
 175   * @return array|bool An array populated with article data, or 'false' in case of no matches
 176   */
 177  
 178  function getNeighbour($threshold, $s, $type, $atts = array(), $threshold_type = 'raw')
 179  {
 180      global $prefs;
 181      static $cache = array();
 182  
 183      $key = md5($threshold.$s.$type.join(n, $atts));
 184  
 185      if (isset($cache[$key])) {
 186          return $cache[$key];
 187      }
 188  
 189      extract($atts);
 190      $expired = ($expired && ($prefs['publish_expired_articles']));
 191      $customFields = getCustomFields();
 192      $thisid = isset($thisid) ? intval($thisid) : 0;
 193  
 194      // Building query parts; lifted from publish.php.
 195      $ids = array_map('intval', do_list($id));
 196      $id = (!$id) ? '' : " AND ID IN (".join(',', $ids).")";
 197      switch ($time) {
 198          case 'any':
 199              $time = "";
 200              break;
 201          case 'future':
 202              $time = " AND Posted > ".now('posted');
 203              break;
 204          default:
 205              $time = " AND Posted <= ".now('posted');
 206      }
 207  
 208      if (!$expired) {
 209          $time .= " AND (".now('expires')." <= Expires OR Expires IS NULL)";
 210      }
 211  
 212      $custom = '';
 213  
 214      if ($customFields) {
 215          foreach ($customFields as $cField) {
 216              if (isset($atts[$cField])) {
 217                  $customPairs[$cField] = $atts[$cField];
 218              }
 219          }
 220  
 221          if (!empty($customPairs)) {
 222              $custom = buildCustomSql($customFields, $customPairs);
 223          }
 224      }
 225  
 226      if ($keywords) {
 227          $keys = doSlash(do_list($keywords));
 228  
 229          foreach ($keys as $key) {
 230              $keyparts[] = "FIND_IN_SET('".$key."', Keywords)";
 231          }
 232  
 233          $keywords = " AND (".join(" OR ", $keyparts).")";
 234      }
 235  
 236      $sortdir = strtolower($sortdir);
 237  
 238      // Invert $type for ascending sortdir.
 239      $types = array(
 240          '>' => array('desc' => '>', 'asc' => '<'),
 241          '<' => array('desc' => '<', 'asc' => '>'),
 242      );
 243  
 244      $type = ($type == '>') ? $types['>'][$sortdir] : $types['<'][$sortdir];
 245  
 246      // Escape threshold and treat it as a string unless explicitly told otherwise.
 247      if ($threshold_type != 'cooked') {
 248          $threshold = "'".doSlash($threshold)."'";
 249      }
 250  
 251      $safe_name = safe_pfx('textpattern');
 252      $q = array(
 253          "SELECT ID AS thisid, Section AS section, Title AS title, url_title, UNIX_TIMESTAMP(Posted) AS posted FROM $safe_name
 254              WHERE ($sortby $type $threshold OR ".($thisid ? "$sortby = $threshold AND ID $type $thisid" : "0").")",
 255          ($s != '' && $s != 'default') ? "AND Section = '".doSlash($s)."'" : filterFrontPage(),
 256          $id,
 257          $time,
 258          $custom,
 259          $keywords,
 260          "AND Status = 4",
 261          "ORDER BY $sortby",
 262          ($type == '<') ? "DESC" : "ASC",
 263          ', ID '.($type == '<' ? 'DESC' : 'ASC'),
 264          "LIMIT 1",
 265      );
 266  
 267      $cache[$key] = getRow(join(n.' ', $q));
 268  
 269      return (is_array($cache[$key])) ? $cache[$key] : false;
 270  }
 271  
 272  /**
 273   * Find next and previous articles relative to a provided threshold level.
 274   *
 275   * @param  int    $id        The "pivot" article's id; use zero (0) to indicate $thisarticle
 276   * @param  scalar $threshold The value to compare against if $id != 0
 277   * @param  string $s         Optional section restriction if $id != 0
 278   * @return array An array populated with article data
 279   */
 280  
 281  function getNextPrev($id = 0, $threshold = null, $s = '')
 282  {
 283      $threshold_type = 'raw';
 284  
 285      if ($id !== 0) {
 286          // Pivot is specific article by ID: In lack of further information,
 287          // revert to default sort order 'Posted desc'.
 288          $atts = filterAtts() + array('sortby' => 'Posted', 'sortdir' => 'DESC', 'thisid' => $id);
 289      } else {
 290          // Pivot is $thisarticle: Use article attributes to find its neighbours.
 291          assert_article();
 292          global $thisarticle;
 293          if (!is_array($thisarticle)) {
 294              return array();
 295          }
 296  
 297          $s = $thisarticle['section'];
 298          $atts = filterAtts() + array('thisid' => $thisarticle['thisid'], 'sort' => 'Posted DESC');
 299          $m = preg_split('/\s+/', $atts['sort']);
 300  
 301          // If in doubt, fall back to chronologically descending order.
 302          if (empty($m[0])            // No explicit sort attribute
 303              || count($m) > 2        // Complex clause, e.g. 'foo asc, bar desc'
 304              || !preg_match('/^(?:[0-9a-zA-Z$_\x{0080}-\x{FFFF}]+|`[\x{0001}-\x{FFFF}]+`)$/u', $m[0])  // The clause's first verb is not a MySQL column identifier.
 305          ) {
 306              $atts['sortby'] = "Posted";
 307              $atts['sortdir'] = "DESC";
 308          } else {
 309              // Sort is like 'foo asc'.
 310              $atts['sortby'] = $m[0];
 311              $atts['sortdir'] = (isset($m[1]) && strtolower($m[1]) == 'desc' ? "DESC" : "ASC");
 312          }
 313  
 314          // Attributes with special treatment.
 315          switch ($atts['sortby']) {
 316              case 'Posted':
 317                  $threshold = "FROM_UNIXTIME(".doSlash($thisarticle['posted']).")";
 318                  $threshold_type = 'cooked';
 319                  break;
 320              case 'Expires':
 321                  $threshold = "FROM_UNIXTIME(".doSlash($thisarticle['expires']).")";
 322                  $threshold_type = 'cooked';
 323                  break;
 324              case 'LastMod':
 325                  $threshold = "FROM_UNIXTIME(".doSlash($thisarticle['modified']).")";
 326                  $threshold_type = 'cooked';
 327                  break;
 328              default:
 329                  // Retrieve current threshold value per sort column from $thisarticle.
 330                  $acm = array_flip(article_column_map());
 331                  $key = $acm[$atts['sortby']];
 332                  $threshold = $thisarticle[$key];
 333                  break;
 334          }
 335      }
 336  
 337      $out['next'] = getNeighbour($threshold, $s, '>', $atts, $threshold_type);
 338      $out['prev'] = getNeighbour($threshold, $s, '<', $atts, $threshold_type);
 339  
 340      return $out;
 341  }
 342  
 343  /**
 344   * Gets the site last modification date.
 345   *
 346   * @return  string
 347   * @package Pref
 348   */
 349  
 350  function lastMod()
 351  {
 352      $last = safe_field("UNIX_TIMESTAMP(val)", 'txp_prefs', "name = 'lastmod' AND prefs_id = 1");
 353  
 354      return gmdate("D, d M Y H:i:s \G\M\T", $last);
 355  }
 356  
 357  /**
 358   * Parse a string and replace any Textpattern tags with their actual value.
 359   *
 360   * @param   string    $thing     The raw string
 361   * @param   null|bool $condition Process true/false part
 362   * @return  string               The parsed string
 363   * @package TagParser
 364   */
 365  
 366  function parse($thing, $condition = null)
 367  {
 368      global $production_status, $trace, $txp_parsed, $txp_else, $txp_current_tag;
 369  
 370      if (isset($condition)) {
 371          if ($production_status === 'debug') {
 372              $trace->log("[$txp_current_tag: ".($condition ? 'true' : 'false') .']');
 373          }
 374      } else {
 375          $condition = true;
 376      }
 377  
 378      if (false === strpos($thing, '<txp:') and false === strpos($thing, '::')) {
 379          return $condition ? $thing : '';
 380      }
 381  
 382      $hash = sha1($thing);
 383  
 384      if (!isset($txp_parsed[$hash])) {
 385          $tags    = array(array());
 386          $tag     = array();
 387          $outside = array();
 388          $inside  = array('');
 389          $else    = array(-1);
 390          $count   = array(-1);
 391          $level   = 0;
 392  
 393          $f = '@(</?(?:txp|[a-z]{3}:):\w+(?:\s+\w+\s*=\s*(?:"(?:[^"]|"")*"|\'(?:[^\']|\'\')*\'|[^\s\'"/>]+))*\s*/?'.chr(62).')@s';
 394          $t = '@^./?(txp|[a-z]{3}:):(\w+)(.*?)/?.$@s';
 395  
 396          $parsed = preg_split($f, $thing, -1, PREG_SPLIT_DELIM_CAPTURE);
 397  
 398          foreach ($parsed as $i => $chunk) {
 399              if ($i&1) {
 400                  preg_match($t, $chunk, $tag[$level]);
 401                  $count[$level] += 2;
 402  
 403                  if ($tag[$level][2] === 'else') {
 404                      $else[$level] = $count[$level];
 405                  }
 406  
 407                  // Handle short tags.
 408                  if (strlen($tag[$level][1]) !== 3 and $tag[$level][1] !== 'txp:' and $tag[$level][2] !== 'else') {
 409                      $tag[$level][2] = $tag[$level][1] . $tag[$level][2];
 410                      $tag[$level][2][3] = '_';
 411                  }
 412  
 413                  if ($chunk[strlen($chunk) - 2] === '/') {
 414                      // Self closed tag.
 415                      $tags[$level][] = array($chunk, $tag[$level][2], $tag[$level][3], null, null);
 416                      $inside[$level] .= $chunk;
 417                  } elseif ($chunk[1] !== '/') {
 418                      // Opening tag.
 419                      $inside[$level] .= $chunk;
 420                      $level++;
 421                      $outside[$level] = $chunk;
 422                      $inside[$level] = '';
 423                      $else[$level] = $count[$level] = -1;
 424                      $tags[$level] = array();
 425                  } else {
 426                      // Closing tag.
 427                      $sha = sha1($inside[$level]);
 428                      $txp_parsed[$sha] = $count[$level] > 2 ? $tags[$level] : false;
 429                      $txp_else[$sha] = array($else[$level] > 0 ? $else[$level] : $count[$level], $count[$level] - 2);
 430                      $level--;
 431                      $tags[$level][] = array($outside[$level+1], $tag[$level][2], $tag[$level][3], $inside[$level+1], $chunk);
 432                      $inside[$level] .= $inside[$level+1] . $chunk;
 433                  }
 434              } else {
 435                  $tags[$level][] = $chunk;
 436                  $inside[$level] .= $chunk;
 437              }
 438          }
 439  
 440          $txp_parsed[$hash] = $count[0] > 0 ? $tags[0] : false;
 441          $txp_else[$hash] = array($else[0] > 0 ? $else[0] : $count[0] + 2, $count[0]);
 442      }
 443  
 444      $tag = $txp_parsed[$hash];
 445  
 446      if (empty($tag)) {
 447          return $condition ? $thing : '';
 448      }
 449  
 450      list($first, $last) = $txp_else[$hash];
 451  
 452      if ($condition) {
 453          $last = $first - 2;
 454          $first   = 1;
 455      } elseif ($first <= $last) {
 456          $first  += 2;
 457      } else {
 458          return '';
 459      }
 460  
 461      for ($out = $tag[$first - 1]; $first <= $last; $first++) {
 462          $t = $tag[$first];
 463          $out .= processTags($t[1], $t[2], $t[3]) . $tag[++$first];
 464      }
 465  
 466      return $out;
 467  }
 468  
 469  /**
 470   * Guesstimate whether a given function name may be a valid tag handler.
 471   *
 472   * @param   string $tag function name
 473   * @return  bool FALSE if the function name is not a valid tag handler
 474   * @package TagParser
 475   */
 476  
 477  function maybe_tag($tag)
 478  {
 479      static $tags = null;
 480  
 481      if ($tags === null) {
 482          $tags = get_defined_functions();
 483          $tags = array_flip($tags['user']);
 484      }
 485  
 486      return isset($tags[$tag]);
 487  }
 488  
 489  /**
 490   * Parse a tag for attributes and hand over to the tag handler function.
 491   *
 492   * @param  string      $tag   The tag name
 493   * @param  string      $atts  The attribute string
 494   * @param  string|null $thing The tag's content in case of container tags
 495   * @return string Parsed tag result
 496   * @package TagParser
 497   */
 498  
 499  function processTags($tag, $atts, $thing = null)
 500  {
 501      global $production_status, $txp_current_tag, $txp_current_form, $trace;
 502      static $registry = null;
 503  
 504      if ($production_status !== 'live') {
 505          $old_tag = $txp_current_tag;
 506          $txp_current_tag = '<txp:'.$tag.$atts.(isset($thing) ? '>' : '/>');
 507          $trace->start($txp_current_tag);
 508      }
 509  
 510      if ($registry === null) {
 511          $registry = Txp::get('\Textpattern\Tag\Registry');
 512      }
 513  
 514      $out = $registry->process($tag, splat($atts), $thing);
 515  
 516      if ($out === false) {
 517          if (maybe_tag($tag)) { // Deprecated in 4.6.0.
 518              trigger_error(gTxt('unregistered_tag'), E_USER_NOTICE);
 519              $out = $registry->register($tag)->process($tag, splat($atts), $thing);
 520          } else {
 521              trigger_error(gTxt('unknown_tag'), E_USER_WARNING);
 522              $out = '';
 523          }
 524      }
 525  
 526      if ($production_status !== 'live') {
 527          $trace->stop(isset($thing) ? "</txp:{$tag}>" : null);
 528          $txp_current_tag = $old_tag;
 529      }
 530  
 531      return $out;
 532  }
 533  
 534  /**
 535   * Protection from those who'd bomb the site by GET.
 536   *
 537   * Origin of the infamous 'Nice try' message and an even more useful '503'
 538   * HTTP status.
 539   */
 540  
 541  function bombShelter()
 542  {
 543      global $prefs;
 544      $in = serverset('REQUEST_URI');
 545  
 546      if (!empty($prefs['max_url_len']) and strlen($in) > $prefs['max_url_len']) {
 547          txp_status_header('503 Service Unavailable');
 548          exit('Nice try.');
 549      }
 550  }
 551  
 552  /**
 553   * Checks a named item's existence in a database table.
 554   *
 555   * The given database table is prefixed with 'txp_'. As such this function can
 556   * only be used with core database tables.
 557   *
 558   * @param   string $table The database table name
 559   * @param   string $val   The name to look for
 560   * @param   bool   $debug Dump the query
 561   * @return  bool|string The item's name, or FALSE when it doesn't exist
 562   * @package Filter
 563   * @example
 564   * if ($r = ckEx('section', 'about'))
 565   * {
 566   *     echo "Section '{$r}' exists.";
 567   * }
 568   */
 569  
 570  function ckEx($table, $val, $debug = false)
 571  {
 572      return safe_field("name", 'txp_'.$table, "name = '".doSlash($val)."' LIMIT 1", $debug);
 573  }
 574  
 575  /**
 576   * Checks if the given category exists.
 577   *
 578   * @param   string $type  The category type, either 'article', 'file', 'link', 'image'
 579   * @param   string $val   The category name to look for
 580   * @param   bool   $debug Dump the query
 581   * @return  bool|string The category's name, or FALSE when it doesn't exist
 582   * @package Filter
 583   * @see     ckEx()
 584   * @example
 585   * if ($r = ckCat('article', 'development'))
 586   * {
 587   *     echo "Category '{$r}' exists.";
 588   * }
 589   */
 590  
 591  function ckCat($type, $val, $debug = false)
 592  {
 593      return safe_field("name", 'txp_category', "name = '".doSlash($val)."' AND type = '".doSlash($type)."' LIMIT 1", $debug);
 594  }
 595  
 596  /**
 597   * Lookup an article by ID.
 598   *
 599   * This function takes an article's ID, and checks if it's been published. If it
 600   * has, returns the section and the ID as an array. FALSE otherwise.
 601   *
 602   * @param   int  $val   The article ID
 603   * @param   bool $debug Dump the query
 604   * @return  array|bool Array of ID and section on success, FALSE otherwise
 605   * @package Filter
 606   * @example
 607   * if ($r = ckExID(36))
 608   * {
 609   *     echo "Article #{$r['id']} is published, and belongs to the section {$r['section']}.";
 610   * }
 611   */
 612  
 613  function ckExID($val, $debug = false)
 614  {
 615      return safe_row("ID, Section", 'textpattern', "ID = ".intval($val)." AND Status >= 4 LIMIT 1", $debug);
 616  }
 617  
 618  /**
 619   * Lookup an article by URL title.
 620   *
 621   * This function takes an article's URL title, and checks if the article has
 622   * been published. If it has, returns the section and the ID as an array.
 623   * FALSE otherwise.
 624   *
 625   * @param   string $val   The URL title
 626   * @param   bool   $debug Dump the query
 627   * @return  array|bool Array of ID and section on success, FALSE otherwise
 628   * @package Filter
 629   * @example
 630   * if ($r = ckExID('my-article-title'))
 631   * {
 632   *     echo "Article #{$r['id']} is published, and belongs to the section {$r['section']}.";
 633   * }
 634   */
 635  
 636  function lookupByTitle($val, $debug = false)
 637  {
 638      return safe_row("ID, Section", 'textpattern', "url_title = '".doSlash($val)."' AND Status >= 4 LIMIT 1", $debug);
 639  }
 640  
 641  /**
 642   * Lookup a published article by URL title and section.
 643   *
 644   * This function takes an article's URL title, and checks if the article has
 645   * been published. If it has, returns the section and the ID as an array.
 646   * FALSE otherwise.
 647   *
 648   * @param   string $val     The URL title
 649   * @param   string $section The section name
 650   * @param   bool   $debug   Dump the query
 651   * @return  array|bool Array of ID and section on success, FALSE otherwise
 652   * @package Filter
 653   * @example
 654   * if ($r = ckExID('my-article-title', 'my-section'))
 655   * {
 656   *     echo "Article #{$r['id']} is published, and belongs to the section {$r['section']}.";
 657   * }
 658   */
 659  
 660  function lookupByTitleSection($val, $section, $debug = false)
 661  {
 662      return safe_row("ID, Section", 'textpattern', "url_title = '".doSlash($val)."' AND Section = '".doSlash($section)."' AND Status >= 4 LIMIT 1", $debug);
 663  }
 664  
 665  /**
 666   * Lookup live article by ID and section.
 667   *
 668   * @param   int    $id      Article ID
 669   * @param   string $section Section name
 670   * @param   bool   $debug
 671   * @return  array|bool
 672   * @package Filter
 673   */
 674  
 675  function lookupByIDSection($id, $section, $debug = false)
 676  {
 677      return safe_row("ID, Section", 'textpattern', "ID = ".intval($id)." AND Section = '".doSlash($section)."' AND Status >= 4 LIMIT 1", $debug);
 678  }
 679  
 680  /**
 681   * Lookup live article by ID.
 682   *
 683   * @param   int  $id    Article ID
 684   * @param   bool $debug
 685   * @return  array|bool
 686   * @package Filter
 687   */
 688  
 689  function lookupByID($id, $debug = false)
 690  {
 691      return safe_row("ID, Section", 'textpattern', "ID = ".intval($id)." AND Status >= 4 LIMIT 1", $debug);
 692  }
 693  
 694  /**
 695   * Lookup live article by date and URL title.
 696   *
 697   * @param   string $when  date wildcard
 698   * @param   string $title URL title
 699   * @param   bool   $debug
 700   * @return  array|bool
 701   * @package Filter
 702   */
 703  
 704  function lookupByDateTitle($when, $title, $debug = false)
 705  {
 706      return safe_row("ID, Section", 'textpattern', "posted LIKE '".doSlash($when)."%' AND url_title LIKE '".doSlash($title)."' AND Status >= 4 LIMIT 1");
 707  }
 708  
 709  /**
 710   * Chops a request string into URL-decoded path parts.
 711   *
 712   * @param   string $req Request string
 713   * @return  array
 714   * @package URL
 715   */
 716  
 717  function chopUrl($req)
 718  {
 719      $req = strtolower($req);
 720  
 721      // Strip off query_string, if present.
 722      $qs = strpos($req, '?');
 723  
 724      if ($qs) {
 725          $req = substr($req, 0, $qs);
 726      }
 727  
 728      $req = preg_replace('/index\.php$/', '', $req);
 729      $r = array_map('urldecode', explode('/', $req));
 730      $o['u0'] = (isset($r[0])) ? $r[0] : '';
 731      $o['u1'] = (isset($r[1])) ? $r[1] : '';
 732      $o['u2'] = (isset($r[2])) ? $r[2] : '';
 733      $o['u3'] = (isset($r[3])) ? $r[3] : '';
 734      $o['u4'] = (isset($r[4])) ? $r[4] : '';
 735  
 736      return $o;
 737  }
 738  
 739  /**
 740   * Save and retrieve the individual article's attributes plus article list
 741   * attributes for next/prev tags.
 742   *
 743   * @param   array $atts
 744   * @return  array
 745   * @since   4.5.0
 746   * @package TagParser
 747   */
 748  
 749  function filterAtts($atts = null)
 750  {
 751      global $prefs, $trace;
 752      static $out = array();
 753  
 754      if (is_array($atts)) {
 755          if (empty($out)) {
 756              $out = lAtts(array(
 757                  'sort'          => 'Posted desc',
 758                  'sortby'        => '',
 759                  'sortdir'        => '',
 760                  'keywords'      => '',
 761                  'expired'       => $prefs['publish_expired_articles'],
 762                  'id'            => '',
 763                  'time'          => 'past',
 764              ), $atts, 0);
 765              $trace->log('[filterAtts accepted]');
 766          } else {
 767              // TODO: deal w/ nested txp:article[_custom] tags.
 768              $trace->log('[filterAtts ignored]');
 769          }
 770      }
 771  
 772      if (empty($out)) {
 773          $trace->log('[filterAtts not set]');
 774      }
 775  
 776      return $out;
 777  }

title

Description

title

Description

title

Description

title

title

Body