Textpattern PHP Cross Reference Content Management Systems

Source: /textpattern/publish.php - 1146 lines - 36124 bytes - Summary - Text - Print

Description: Site hostname.

   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  if (!defined('txpath')) {
  26      define("txpath", dirname(__FILE__));
  27  }
  28  
  29  if (!defined("txpinterface")) {
  30      die('If you just updated and expect to see your site here, please also update the files in your main installation directory.'.
  31          ' (Otherwise note that publish.php cannot be called directly.)');
  32  }
  33  
  34  global $trace;
  35  
  36  $trace->start('[PHP includes, stage 2]');
  37  include_once txpath.'/vendors/Textpattern/Loader.php';
  38  
  39  $loader = new \Textpattern\Loader(txpath.'/vendors');
  40  $loader->register();
  41  
  42  $loader = new \Textpattern\Loader(txpath.'/lib');
  43  $loader->register();
  44  
  45  include_once txpath.'/lib/txplib_publish.php';
  46  include_once txpath.'/lib/txplib_db.php';
  47  include_once txpath.'/lib/txplib_html.php';
  48  include_once txpath.'/lib/txplib_forms.php';
  49  include_once txpath.'/lib/admin_config.php';
  50  
  51  include_once txpath.'/publish/taghandlers.php';
  52  include_once txpath.'/publish/log.php';
  53  include_once txpath.'/publish/comment.php';
  54  $trace->stop();
  55  
  56  set_error_handler('publicErrorHandler', error_reporting());
  57  
  58  ob_start();
  59  
  60  $txp_current_tag = '';
  61  $txp_parsed      = array();
  62  
  63  // Get all prefs as an array.
  64  $prefs = get_prefs();
  65  
  66  // Add prefs to globals.
  67  extract($prefs);
  68  
  69  // Check the size of the URL request.
  70  bombShelter();
  71  
  72  // Set a higher error level during initialisation.
  73  set_error_level(@$production_status == 'live' ? 'testing' : @$production_status);
  74  
  75  // disable tracing in live environment.
  76  if ($production_status == 'live') {
  77      Trace::setQuiet(true);
  78  }
  79  
  80  // Use the current URL path if $siteurl is unknown.
  81  if (empty($siteurl)) {
  82      $httphost = preg_replace('/[^-_a-zA-Z0-9.:]/', '', $_SERVER['HTTP_HOST']);
  83      $prefs['siteurl'] = $siteurl = $httphost.rtrim(dirname($_SERVER['SCRIPT_NAME']), DS);
  84  }
  85  
  86  if (empty($path_to_site)) {
  87      updateSitePath(dirname(dirname(__FILE__)));
  88  }
  89  
  90  if (!defined('PROTOCOL')) {
  91      switch (serverSet('HTTPS')) {
  92          case '':
  93          case 'off': // ISAPI with IIS.
  94              define('PROTOCOL', 'http://');
  95              break;
  96          default:
  97              define('PROTOCOL', 'https://');
  98              break;
  99      }
 100  }
 101  
 102  // Definitive HTTP address of the site.
 103  if (!defined('hu')) {
 104      define('hu', PROTOCOL.$siteurl.'/');
 105  }
 106  
 107  // Relative URL global.
 108  if (!defined('rhu')) {
 109      define('rhu', preg_replace('|^https?://[^/]+|', '', hu));
 110  }
 111  
 112  // HTTP address of the site serving images.
 113  if (!defined('ihu')) {
 114      define('ihu', hu);
 115  }
 116  
 117  if (!defined('SITE_HOST')) {
 118      /**
 119       * Site hostname.
 120       *
 121       * @package Network
 122       * @since   4.6.0
 123       */
 124  
 125      define('SITE_HOST', (string) @parse_url(hu, PHP_URL_HOST));
 126  }
 127  
 128  if (!defined('IMPATH')) {
 129      /**
 130       * Path to image directory.
 131       *
 132       * @package Image
 133       */
 134  
 135      define('IMPATH', $path_to_site.DS.$img_dir.DS);
 136  }
 137  
 138  // 1.0: a new $here variable in the top-level index.php should let us know the
 139  // server path to the live site let's save it to prefs.
 140  if (isset($here) and $path_to_site != $here) {
 141      updateSitePath($here);
 142  }
 143  
 144  if (!defined('LANG')) {
 145      /**
 146       * Currently active language.
 147       *
 148       * @package L10n
 149       */
 150  
 151      define('LANG', $language);
 152  }
 153  
 154  if (!empty($locale)) {
 155      setlocale(LC_ALL, $locale);
 156  }
 157  
 158  // Initialise the current user.
 159  $txp_user = null;
 160  
 161  // i18n.
 162  $textarray = (txpinterface == 'css') ? array() : load_lang(LANG);
 163  
 164  // Tidy up the site.
 165  janitor();
 166  
 167  // Here come the plugins.
 168  if ($use_plugins) {
 169      load_plugins();
 170  }
 171  
 172  // This step deprecated as of 1.0 - really only useful with old-style section
 173  // placeholders, which passed $s='section_name'.
 174  $s = (empty($s)) ? '' : $s;
 175  
 176  $pretext = !isset($pretext) ? array() : $pretext;
 177  $pretext = array_merge($pretext, pretext($s, $prefs));
 178  callback_event('pretext_end');
 179  extract($pretext);
 180  
 181  // Now that everything is initialised, we can crank down error reporting.
 182  set_error_level($production_status);
 183  
 184  if (!empty($feed) && in_array($feed, array('atom', 'rss'), true)) {
 185      include txpath."/publish/{$feed}.php";
 186      echo $feed();
 187  
 188      if ($production_status !== 'live') {
 189          echo $trace->summary();
 190  
 191          if ($production_status === 'debug') {
 192              echo $trace->result();
 193          }
 194      }
 195  
 196      exit;
 197  }
 198  
 199  if (gps('parentid')) {
 200      if (ps('submit')) {
 201          saveComment();
 202      } elseif (ps('preview')) {
 203          checkCommentRequired(getComment());
 204      } elseif ($comments_mode == 1) {
 205          // Popup comments?
 206          header("Content-type: text/html; charset=utf-8");
 207          exit(parse_form('popup_comments'));
 208      }
 209  }
 210  
 211  // We are dealing with a download.
 212  if (@$s == 'file_download' && !empty($filename)) {
 213      output_file_download($filename);
 214      exit(0);
 215  }
 216  
 217  // Send 304 Not Modified if appropriate.
 218  handle_lastmod();
 219  
 220  // Log the page view.
 221  log_hit($status);
 222  
 223  // -------------------------------------------------------------
 224  
 225  function preText($s, $prefs)
 226  {
 227      extract($prefs);
 228  
 229      callback_event('pretext');
 230  
 231      // Set messy variables.
 232      $out = makeOut('id', 's', 'c', 'context', 'q', 'm', 'pg', 'p', 'month', 'author');
 233  
 234      if (gps('rss')) {
 235          $out['feed'] = 'rss';
 236      }
 237  
 238      if (gps('atom')) {
 239          $out['feed'] = 'atom';
 240      }
 241  
 242      // Some useful vars for taghandlers, plugins.
 243      $out['request_uri'] = preg_replace("|^https?://[^/]+|i", "", serverSet('REQUEST_URI'));
 244      $out['qs'] = serverSet('QUERY_STRING');
 245  
 246      // IIS fix.
 247      if (!$out['request_uri'] and serverSet('SCRIPT_NAME')) {
 248          $out['request_uri'] = serverSet('SCRIPT_NAME').((serverSet('QUERY_STRING')) ? '?'.serverSet('QUERY_STRING') : '');
 249      }
 250  
 251      // Another IIS fix.
 252      if (!$out['request_uri'] and serverSet('argv')) {
 253          $argv = serverSet('argv');
 254          $out['request_uri'] = @substr($argv[0], strpos($argv[0], ';') + 1);
 255      }
 256  
 257      // Define the useable url, minus any subdirectories.
 258      // This is pretty ugly, if anyone wants to have a go at it.
 259      $out['subpath'] = $subpath = preg_quote(preg_replace("/https?:\/\/.*(\/.*)/Ui", "$1", hu), "/");
 260      $out['req'] = $req = preg_replace("/^$subpath/i", "/", $out['request_uri']);
 261  
 262      $is_404 = ($out['status'] == '404');
 263  
 264      // If messy vars exist, bypass URL parsing.
 265      if (!$out['id'] && !$out['s'] && !(txpinterface == 'css') && ! (txpinterface == 'admin')) {
 266          // Return clean URL test results for diagnostics.
 267          if (gps('txpcleantest')) {
 268              exit(show_clean_test($out));
 269          }
 270  
 271          extract(chopUrl($req));
 272  
 273          // First we sniff out some of the preset URL schemes.
 274          if (strlen($u1)) {
 275              switch ($u1) {
 276                  case 'atom':
 277                      $out['feed'] = 'atom';
 278                      break;
 279  
 280                  case 'rss':
 281                      $out['feed'] = 'rss';
 282                      break;
 283  
 284                  // urldecode(strtolower(urlencode())) looks ugly but is the
 285                  // only way to make it multibyte-safe without breaking
 286                  // backwards-compatibility.
 287                  case urldecode(strtolower(urlencode(gTxt('section')))):
 288                      $out['s'] = (ckEx('section', $u2)) ? $u2 : ''; $is_404 = empty($out['s']);
 289                      break;
 290  
 291                  case urldecode(strtolower(urlencode(gTxt('category')))):
 292                      if ($u3) {
 293                          $out['context'] = validContext($u2);
 294                          $out['c'] = $u3;
 295                      } else {
 296                          $out['context'] = 'article';
 297                          $out['c'] = $u2;
 298                      }
 299                      $out['c'] = (ckCat($out['context'], $out['c'])) ? $out['c'] : '';
 300                      $is_404 = empty($out['c']);
 301                      break;
 302  
 303                  case urldecode(strtolower(urlencode(gTxt('author')))):
 304                      if ($u3) {
 305                          $out['context'] = validContext($u2);
 306                          $out['author'] = $u3;
 307                      } else {
 308                          $out['context'] = 'article';
 309                          $out['author'] = $u2;
 310                      }
 311  
 312                      $out['author'] = (!empty($out['author'])) ? $out['author'] : '';
 313                      break;
 314                      // AuthorID gets resolved from Name further down.
 315  
 316                  case urldecode(strtolower(urlencode(gTxt('file_download')))):
 317                      $out['s'] = 'file_download';
 318                      $out['id'] = (!empty($u2)) ? $u2 : '';
 319                      $out['filename'] = (!empty($u3)) ? $u3 : '';
 320                      break;
 321  
 322                  default:
 323                      // Then see if the prefs-defined permlink scheme is usable.
 324                      switch ($permlink_mode) {
 325  
 326                          case 'section_id_title':
 327                              if (empty($u2)) {
 328                                  $out['s'] = (ckEx('section', $u1)) ? $u1 : '';
 329                                  $is_404 = empty($out['s']);
 330                              } else {
 331                                  $rs = lookupByIDSection($u2, $u1);
 332                                  $out['s'] = @$rs['Section'];
 333                                  $out['id'] = @$rs['ID'];
 334                                  $is_404 = (empty($out['s']) or empty($out['id']));
 335                              }
 336  
 337                              break;
 338  
 339                          case 'year_month_day_title':
 340                              if (empty($u2)) {
 341                                  $out['s'] = (ckEx('section', $u1)) ? $u1 : '';
 342                                  $is_404 = empty($out['s']);
 343                              } elseif (empty($u4)) {
 344                                  $month = "$u1-$u2";
 345  
 346                                  if (!empty($u3)) {
 347                                      $month .= "-$u3";
 348                                  }
 349  
 350                                  if (preg_match('/\d+-\d+(?:-\d+)?/', $month)) {
 351                                      $out['month'] = $month;
 352                                      $out['s'] = 'default';
 353                                  } else {
 354                                      $is_404 = 1;
 355                                  }
 356                              } else {
 357                                  $when = "$u1-$u2-$u3";
 358                                  $rs = lookupByDateTitle($when, $u4);
 359                                  $out['id'] = (!empty($rs['ID'])) ? $rs['ID'] : '';
 360                                  $out['s'] = (!empty($rs['Section'])) ? $rs['Section'] : '';
 361                                  $is_404 = (empty($out['s']) or empty($out['id']));
 362                              }
 363  
 364                              break;
 365  
 366                          case 'section_title':
 367                              if (empty($u2)) {
 368                                  $out['s'] = (ckEx('section', $u1)) ? $u1 : '';
 369                                  $is_404 = empty($out['s']);
 370                              } else {
 371                                  $rs = lookupByTitleSection($u2, $u1);
 372                                  $out['id'] = isset($rs['ID']) ? $rs['ID'] : '';
 373                                  $out['s'] = isset($rs['Section']) ? $rs['Section'] : '';
 374                                  $is_404 = (empty($out['s']) or empty($out['id']));
 375                              }
 376  
 377                              break;
 378  
 379                          case 'title_only':
 380                              $rs = lookupByTitle($u1);
 381                              $out['id'] = @$rs['ID'];
 382                              $out['s'] = (empty($rs['Section']) ? ckEx('section', $u1) :
 383                                      $rs['Section']);
 384                              $is_404 = empty($out['s']);
 385  
 386                              break;
 387  
 388                          case 'id_title':
 389                              if (is_numeric($u1) && ckExID($u1)) {
 390                                  $rs = lookupByID($u1);
 391                                  $out['id'] = (!empty($rs['ID'])) ? $rs['ID'] : '';
 392                                  $out['s'] = (!empty($rs['Section'])) ? $rs['Section'] : '';
 393                                  $is_404 = (empty($out['s']) or empty($out['id']));
 394                              } else {
 395                                  // We don't want to miss the /section/ pages.
 396                                  $out['s'] = ckEx('section', $u1) ? $u1 : '';
 397                                  $is_404 = empty($out['s']);
 398                              }
 399  
 400                              break;
 401                      }
 402  
 403                      if (!$is_404) {
 404                          $out['context'] = validContext($out['context']);
 405                      }
 406  
 407                      break; // Prefs-defined permlink scheme case.
 408              }
 409          } else {
 410              $out['s'] = 'default';
 411              $out['context'] = validContext($out['context']);
 412          }
 413      } else {
 414          // Messy mode, but prevent to get the id for file_downloads.
 415          $out['context'] = validContext($out['context']);
 416  
 417          if ($out['context'] == 'article' && $out['id'] && $out['s'] != 'file_download') {
 418              $rs = lookupByID($out['id']);
 419              $out['id'] = (!empty($rs['ID'])) ? $rs['ID'] : '';
 420              $out['s'] = (!empty($rs['Section'])) ? $rs['Section'] : '';
 421              $is_404 = (empty($out['s']) or empty($out['id']));
 422          }
 423      }
 424  
 425      // Existing category in messy or clean URL?
 426      if (!empty($out['c'])) {
 427          if (!ckCat($out['context'], $out['c'])) {
 428              $is_404 = true;
 429              $out['c'] = '';
 430          }
 431      }
 432  
 433      // Resolve AuthorID from Authorname.
 434      if ($out['author']) {
 435          $name = urldecode(strtolower(urlencode($out['author'])));
 436  
 437          $name = safe_field('name', 'txp_users', "RealName LIKE '".doSlash($out['author'])."'");
 438  
 439          if ($name) {
 440              $out['author'] = $name;
 441          } else {
 442              $out['author'] = '';
 443              $is_404 = true;
 444          }
 445      }
 446  
 447      // Allow article preview.
 448      if (gps('txpreview')) {
 449          doAuth();
 450  
 451          if (!has_privs('article.preview')) {
 452              txp_status_header('401 Unauthorized');
 453              exit(hed('401 Unauthorized', 1).graf(gTxt('restricted_area')));
 454          }
 455  
 456          global $nolog;
 457  
 458          $nolog = true;
 459          $rs = safe_row("ID AS id, Section AS s", 'textpattern', "ID = ".intval(gps('txpreview'))." LIMIT 1");
 460  
 461          if ($rs) {
 462              $is_404 = false;
 463              $out = array_merge($out, $rs);
 464          }
 465      }
 466  
 467      // Stats: found or not.
 468      $out['status'] = ($is_404 ? '404' : '200');
 469  
 470      $out['pg'] = is_numeric($out['pg']) ? intval($out['pg']) : '';
 471      $out['id'] = is_numeric($out['id']) ? intval($out['id']) : '';
 472  
 473      if ($out['s'] == 'file_download') {
 474          if (is_numeric($out['id'])) {
 475              // Undo the double-encoding workaround for .gz files;
 476              // @see filedownloadurl().
 477              if (!empty($out['filename'])) {
 478                  $out['filename'] = preg_replace('/gz&$/i', 'gz', $out['filename']);
 479              }
 480  
 481              $fn = empty($out['filename']) ? '' : " AND filename = '".doSlash($out['filename'])."'";
 482              $rs = safe_row('*', 'txp_file', "id = ".intval($out['id'])." AND status = ".STATUS_LIVE." AND created <= ".now('created').$fn);
 483          }
 484  
 485          return (!empty($rs)) ? array_merge($out, $rs) : array('s' => 'file_download', 'file_error' => 404);
 486      }
 487  
 488      if (!$is_404) {
 489          $out['s'] = (empty($out['s'])) ? 'default' : $out['s'];
 490      }
 491      $s = $out['s'];
 492      $id = $out['id'];
 493  
 494      // Hackish.
 495      global $is_article_list;
 496      if (empty($id)) {
 497          $is_article_list = true;
 498      }
 499  
 500      // By this point we should know the section, so grab its page and CSS.
 501      if (txpinterface != 'css') {
 502          $rs = safe_row("page, css", "txp_section", "name = '".doSlash($s)."' LIMIT 1");
 503          $out['page'] = isset($rs['page']) ? $rs['page'] : '';
 504          $out['css'] = isset($rs['css']) ? $rs['css'] : '';
 505      }
 506  
 507      if (is_numeric($id) and !$is_404) {
 508          $a = safe_row("*, UNIX_TIMESTAMP(Posted) AS uPosted, UNIX_TIMESTAMP(Expires) AS uExpires, UNIX_TIMESTAMP(LastMod) AS uLastMod",
 509              'textpattern',
 510              "ID = ".intval($id).(gps('txpreview') ? '' : " AND Status IN (".STATUS_LIVE.",".STATUS_STICKY.")"));
 511  
 512          if ($a) {
 513              $out['id_keywords'] = $a['Keywords'];
 514              $out['id_author']   = $a['AuthorID'];
 515              populateArticleData($a);
 516  
 517              $uExpires = $a['uExpires'];
 518  
 519              if ($uExpires and time() > $uExpires and !$publish_expired_articles) {
 520                  $out['status'] = '410';
 521              }
 522          }
 523      }
 524  
 525      // These are deprecated as of Textpattern v1.0 - leaving them here for
 526      // plugin compatibility.
 527      $out['path_from_root'] = rhu;
 528      $out['pfr']            = rhu;
 529  
 530      $out['path_to_site']   = $path_to_site;
 531      $out['permlink_mode']  = $permlink_mode;
 532      $out['sitename']       = $sitename;
 533  
 534      return $out;
 535  }
 536  
 537  //    textpattern() is the function that assembles a page, based on
 538  //    the variables passed to it by pretext();
 539  
 540  // -------------------------------------------------------------
 541  
 542  function textpattern()
 543  {
 544      global $pretext, $prefs, $production_status, $siteurl, $has_article_tag;
 545  
 546      $has_article_tag = false;
 547  
 548      callback_event('textpattern');
 549  
 550      if ($pretext['status'] == '404') {
 551          txp_die(gTxt('404_not_found'), '404');
 552      }
 553  
 554      if ($pretext['status'] == '410') {
 555          txp_die(gTxt('410_gone'), '410');
 556      }
 557  
 558      // Useful for clean URLs with error-handlers.
 559      txp_status_header('200 OK');
 560  
 561      set_error_handler('tagErrorHandler');
 562      $html = parse_page($pretext['page']);
 563  
 564      if ($html === false) {
 565          txp_die(gTxt('unknown_section'), '404');
 566      }
 567  
 568      // Make sure the page has an article tag if necessary.
 569      if (!$has_article_tag and $production_status != 'live' and $pretext['context'] == 'article' and (!empty($pretext['id']) or !empty($pretext['c']) or !empty($pretext['q']) or !empty($pretext['pg']))) {
 570          trigger_error(gTxt('missing_article_tag', array('{page}' => $pretext['page'])));
 571      }
 572  
 573      restore_error_handler();
 574  
 575      header("Content-type: text/html; charset=utf-8");
 576      echo $html;
 577  
 578      callback_event('textpattern_end');
 579  }
 580  
 581  // -------------------------------------------------------------
 582  function output_css($s = '', $n = '')
 583  {
 584      $order = '';
 585  
 586      if ($n) {
 587          if (!is_scalar($n)) {
 588              txp_die('Not Found', 404);
 589          }
 590  
 591          $n = do_list_unique($n);
 592          $cssname = join("','", doSlash($n));
 593  
 594          if (count($n) > 1) {
 595              $order = " ORDER BY FIELD(name, '$cssname')";
 596          }
 597      } elseif ($s) {
 598          if (!is_scalar($s)) {
 599              txp_die('Not Found', 404);
 600          }
 601  
 602          $cssname = safe_field('css', 'txp_section', "name = '".doSlash($s)."'");
 603      }
 604  
 605      if (!empty($cssname)) {
 606          $css = join(n, safe_column_num('css', 'txp_css', "name IN ('$cssname')".$order));
 607          echo $css;
 608      }
 609  }
 610  
 611  // -------------------------------------------------------------
 612  function output_file_download($filename)
 613  {
 614      global $file_error, $file_base_path, $pretext;
 615  
 616      callback_event('file_download');
 617  
 618      if (!isset($file_error)) {
 619          $filename = sanitizeForFile($filename);
 620          $fullpath = build_file_path($file_base_path, $filename);
 621  
 622          if (is_file($fullpath)) {
 623              // Discard any error PHP messages.
 624              ob_clean();
 625              $filesize = filesize($fullpath);
 626              $sent = 0;
 627              header('Content-Description: File Download');
 628              header('Content-Type: application/octet-stream');
 629              header('Content-Disposition: attachment; filename="'.$filename.'"; size = "'.$filesize.'"');
 630  
 631              // Fix for IE6 PDF bug on servers configured to send cache headers.
 632              header('Cache-Control: private');
 633              @ini_set("zlib.output_compression", "Off");
 634              @set_time_limit(0);
 635              @ignore_user_abort(true);
 636  
 637              if ($file = fopen($fullpath, 'rb')) {
 638                  while (!feof($file) and (connection_status() == 0)) {
 639                      echo fread($file, 1024 * 64);
 640                      $sent += (1024 * 64);
 641                      ob_flush();
 642                      flush();
 643                  }
 644  
 645                  fclose($file);
 646  
 647                  // Record download.
 648                  if ((connection_status() == 0) and !connection_aborted()) {
 649                      safe_update('txp_file', "downloads = downloads + 1", "id = ".intval($pretext['id']));
 650                  } else {
 651                      $pretext['request_uri'] .= ($sent >= $filesize)
 652                          ? '#aborted'
 653                          : "#aborted-at-".floor($sent * 100 / $filesize)."%";
 654                  }
 655  
 656                  log_hit('200');
 657              }
 658          } else {
 659              $file_error = 404;
 660          }
 661      }
 662  
 663      // Deal with error.
 664      if (isset($file_error)) {
 665          switch ($file_error) {
 666          case 403:
 667              txp_die(gTxt('403_forbidden'), '403');
 668              break;
 669          case 404:
 670              txp_die(gTxt('404_not_found'), '404');
 671              break;
 672          default:
 673              txp_die(gTxt('500_internal_server_error'), '500');
 674              break;
 675          }
 676      }
 677  }
 678  
 679  // article() is called when parse() finds a <txp:article /> tag.
 680  // If an $id has been established, we output a single article,
 681  // otherwise, output a list.
 682  
 683  // -------------------------------------------------------------
 684  function article($atts, $thing = null)
 685  {
 686      global $is_article_body, $has_article_tag;
 687  
 688      if ($is_article_body) {
 689          trigger_error(gTxt('article_tag_illegal_body'));
 690  
 691          return '';
 692      }
 693  
 694      $has_article_tag = true;
 695  
 696      return parseArticles($atts, '0', $thing);
 697  }
 698  
 699  // -------------------------------------------------------------
 700  
 701  function doArticles($atts, $iscustom, $thing = null)
 702  {
 703      global $pretext, $prefs;
 704      extract($pretext);
 705      extract($prefs);
 706      $customFields = getCustomFields();
 707      $customlAtts = array_null(array_flip($customFields));
 708  
 709      if ($iscustom) {
 710  
 711          // Custom articles must not render search results.
 712          $q = '';
 713  
 714          $extralAtts = array(
 715              'category'  => '',
 716              'section'   => '',
 717              'excerpted' => '',
 718              'author'    => '',
 719              'month'     => '',
 720              'expired'   => $publish_expired_articles,
 721              'id'        => '',
 722              'exclude'   => '',
 723          );
 724      } else {
 725          $extralAtts = array(
 726              'listform'     => '',
 727              'searchform'   => '',
 728              'searchall'    => 1,
 729              'searchsticky' => 0,
 730              'pageby'       => '',
 731              'pgonly'       => 0,
 732          );
 733      }
 734  
 735      // Getting attributes.
 736      $theAtts = lAtts(array(
 737          'form'          => 'default',
 738          'limit'         => 10,
 739          'sort'          => '',
 740          'sortby'        => '', // Deprecated in 4.0.4.
 741          'sortdir'       => '', // Deprecated in 4.0.4.
 742          'keywords'      => '',
 743          'time'          => 'past',
 744          'status'        => STATUS_LIVE,
 745          'allowoverride' => !$iscustom,
 746          'frontpage'     => !$iscustom,
 747          'match'         => 'Category1,Category2',
 748          'offset'        => 0,
 749          'wraptag'       => '',
 750          'break'         => '',
 751          'label'         => '',
 752          'labeltag'      => '',
 753          'class'         => '',
 754      ) + $customlAtts + $extralAtts, $atts);
 755  
 756      // For the txp:article tag, some attributes are taken from globals;
 757      // override them, then stash all filter attributes.
 758      if (!$iscustom) {
 759          $theAtts['category'] = ($c) ? $c : '';
 760          $theAtts['section'] = ($s && $s != 'default') ? $s : '';
 761          $theAtts['author'] = (!empty($author) ? $author : '');
 762          $theAtts['month'] = (!empty($month) ? $month : '');
 763          $theAtts['frontpage'] = ($theAtts['frontpage'] && $s && $s == 'default');
 764          $theAtts['excerpted'] = 0;
 765          $theAtts['exclude'] = 0;
 766          $theAtts['expired'] = $publish_expired_articles;
 767  
 768          filterAtts($theAtts);
 769      }
 770  
 771      extract($theAtts);
 772  
 773      // If a listform is specified, $thing is for doArticle() - hence ignore here.
 774      if (!empty($listform)) {
 775          $thing = '';
 776      }
 777  
 778      $pageby = (empty($pageby) ? $limit : $pageby);
 779  
 780      // Treat sticky articles differently wrt search filtering, etc.
 781      $status = in_array(strtolower($status), array('sticky', STATUS_STICKY)) ? STATUS_STICKY : STATUS_LIVE;
 782      $issticky = ($status == STATUS_STICKY);
 783  
 784      // Give control to search, if necessary.
 785      if ($q && !$issticky) {
 786          include_once txpath.'/publish/search.php';
 787  
 788          $s_filter = ($searchall ? filterSearch() : '');
 789          $q = trim($q);
 790          $quoted = ($q[0] === '"') && ($q[strlen($q) - 1] === '"');
 791          $q = doSlash($quoted ? trim(trim($q, '"')) : $q);
 792  
 793          // Searchable article fields are limited to the columns of the
 794          // textpattern table and a matching fulltext index must exist.
 795          $cols = do_list_unique($searchable_article_fields);
 796  
 797          if (empty($cols) or $cols[0] == '') {
 798              $cols = array('Title', 'Body');
 799          }
 800  
 801          $score = ", MATCH (`".join("`, `", $cols)."`) AGAINST ('$q') AS score";
 802          $search_terms = preg_replace('/\s+/', ' ', str_replace(array('\\', '%', '_', '\''), array('\\\\', '\\%', '\\_', '\\\''), $q));
 803  
 804          if ($quoted || empty($m) || $m === 'exact') {
 805              for ($i = 0; $i < count($cols); $i++) {
 806                  $cols[$i] = "`$cols[$i]` LIKE '%$search_terms%'";
 807              }
 808          } else {
 809              $colJoin = ($m === 'any') ? "OR" : "AND";
 810              $search_terms = explode(' ', $search_terms);
 811              for ($i = 0; $i < count($cols); $i++) {
 812                  $like = array();
 813                  foreach ($search_terms as $search_term) {
 814                      $like[] = "`$cols[$i]` LIKE '%$search_term%'";
 815                  }
 816                  $cols[$i] = "(".join(" $colJoin ", $like).")";
 817              }
 818          }
 819  
 820          $cols = join(" OR ", $cols);
 821          $search = " AND ($cols) $s_filter";
 822  
 823          // searchall=0 can be used to show search results for the current
 824          // section only.
 825          if ($searchall) {
 826              $section = '';
 827          }
 828  
 829          if (!$sort) {
 830              $sort = "score DESC";
 831          }
 832      } else {
 833          $score = $search = '';
 834  
 835          if (!$sort) {
 836              $sort = "Posted DESC";
 837          }
 838      }
 839  
 840      // For backwards compatibility. sortby and sortdir are deprecated.
 841      if ($sortby) {
 842          trigger_error(gTxt('deprecated_attribute', array('{name}' => 'sortby')), E_USER_NOTICE);
 843  
 844          if (!$sortdir) {
 845              $sortdir = "DESC";
 846          } else {
 847              trigger_error(gTxt('deprecated_attribute', array('{name}' => 'sortdir')), E_USER_NOTICE);
 848          }
 849  
 850          $sort = "$sortby $sortdir";
 851      } elseif ($sortdir) {
 852          trigger_error(gTxt('deprecated_attribute', array('{name}' => 'sortdir')), E_USER_NOTICE);
 853          $sort = "Posted $sortdir";
 854      }
 855  
 856      // Building query parts.
 857      $frontpage = ($frontpage and (!$q or $issticky)) ? filterFrontPage() : '';
 858      $category  = join("','", doSlash(do_list_unique($category)));
 859      $categories = array();
 860      $match = do_list_unique($match);
 861  
 862      if (in_array('Category1', $match)) {
 863          $categories[] = "Category1 IN ('$category')";
 864      }
 865  
 866      if (in_array('Category2', $match)) {
 867          $categories[] = "Category2 IN ('$category')";
 868      }
 869  
 870      $categories = join(" OR ", $categories);
 871      $category  = (!$category or !$categories)  ? '' : " AND ($categories)";
 872      $section   = (!$section)   ? '' : " AND Section IN ('".join("','", doSlash(do_list_unique($section)))."')";
 873      $excerpted = (!$excerpted) ? '' : " AND Excerpt !=''";
 874      $author    = (!$author)    ? '' : " AND AuthorID IN ('".join("','", doSlash(do_list_unique($author)))."')";
 875      $month     = (!$month)     ? '' : " AND Posted LIKE '".doSlash($month)."%'";
 876      $ids = $id ? array_map('intval', do_list_unique($id)) : array();
 877      $exclude = $exclude ? array_map('intval', do_list_unique($exclude)) : array();
 878      $id        = ((!$id)        ? '' : " AND ID IN (".join(',', $ids).")")
 879          .((!$exclude)   ? '' : " AND ID NOT IN (".join(',', $exclude).")");
 880  
 881      switch ($time) {
 882          case 'any':
 883              $time = "";
 884              break;
 885          case 'future':
 886              $time = " AND Posted > ".now('posted');
 887              break;
 888          default:
 889              $time = " AND Posted <= ".now('posted');
 890      }
 891  
 892      if (!$expired) {
 893          $time .= " AND (".now('expires')." <= Expires OR Expires IS NULL)";
 894      }
 895  
 896      $custom = '';
 897  
 898      if ($customFields) {
 899          foreach ($customFields as $cField) {
 900              if (isset($atts[$cField])) {
 901                  $customPairs[$cField] = $atts[$cField];
 902              }
 903          }
 904  
 905          if (!empty($customPairs)) {
 906              $custom = buildCustomSql($customFields, $customPairs);
 907          }
 908      }
 909  
 910      // Allow keywords for no-custom articles. That tagging mode, you know.
 911      if ($keywords) {
 912          $keys = doSlash(do_list_unique($keywords));
 913  
 914          foreach ($keys as $key) {
 915              $keyparts[] = "FIND_IN_SET('".$key."', Keywords)";
 916          }
 917  
 918          $keywords = " AND (".join(' or ', $keyparts).")";
 919      }
 920  
 921      if ($q && $searchsticky) {
 922          $statusq = " AND Status >= ".STATUS_LIVE;
 923      } elseif ($id) {
 924          $statusq = " AND Status >= ".STATUS_LIVE;
 925      } else {
 926          $statusq = " AND Status = ".intval($status);
 927      }
 928  
 929      $where = "1 = 1".$statusq.$time.
 930          $search.$id.$category.$section.$excerpted.$month.$author.$keywords.$custom.$frontpage;
 931  
 932      // Do not paginate if we are on a custom list.
 933      if (!$iscustom and !$issticky) {
 934          $grand_total = safe_count('textpattern', $where);
 935          $total = $grand_total - $offset;
 936          $numPages = ceil($total / $pageby);
 937          $pg = (!$pg) ? 1 : $pg;
 938          $pgoffset = $offset + (($pg - 1) * $pageby);
 939  
 940          // Send paging info to txp:newer and txp:older.
 941          $pageout['pg']          = $pg;
 942          $pageout['numPages']    = $numPages;
 943          $pageout['s']           = $s;
 944          $pageout['c']           = $c;
 945          $pageout['context']     = 'article';
 946          $pageout['grand_total'] = $grand_total;
 947          $pageout['total']       = $total;
 948  
 949          global $thispage;
 950  
 951          if (empty($thispage)) {
 952              $thispage = $pageout;
 953          }
 954  
 955          if ($pgonly) {
 956              return;
 957          }
 958      } else {
 959          $pgoffset = $offset;
 960      }
 961  
 962      // Preserve order of custom article ids unless 'sort' attribute is set.
 963      if (!empty($atts['id']) && empty($atts['sort'])) {
 964          $safe_sort = "FIELD(id, ".join(',', $ids).")";
 965      } else {
 966          $safe_sort = doSlash($sort);
 967      }
 968  
 969      $rs = safe_rows_start("*, UNIX_TIMESTAMP(Posted) AS uPosted, UNIX_TIMESTAMP(Expires) AS uExpires, UNIX_TIMESTAMP(LastMod) AS uLastMod".$score,
 970          'textpattern',
 971          "$where ORDER BY $safe_sort LIMIT ".intval($pgoffset).", ".intval($limit));
 972  
 973      // Get the form name.
 974      if ($q && !$issticky) {
 975          $fname = ($searchform ? $searchform : 'search_results');
 976      } else {
 977          $fname = (!empty($listform) ? $listform : $form);
 978      }
 979  
 980      if ($rs) {
 981          $count = 0;
 982          $last = numRows($rs);
 983  
 984          $articles = array();
 985  
 986          while ($a = nextRow($rs)) {
 987              ++$count;
 988              populateArticleData($a);
 989              global $thisarticle, $uPosted, $limit;
 990              $thisarticle['is_first'] = ($count == 1);
 991              $thisarticle['is_last'] = ($count == $last);
 992  
 993              // Article form preview.
 994              if (txpinterface === 'admin' && ps('Form')) {
 995                  doAuth();
 996  
 997                  if (!has_privs('form')) {
 998                      txp_status_header('401 Unauthorized');
 999                      exit(hed('401 Unauthorized', 1).graf(gTxt('restricted_area')));
1000                  }
1001  
1002                  $articles[] = parse(gps('Form'));
1003              } elseif ($allowoverride and $a['override_form']) {
1004                  $articles[] = parse_form($a['override_form']);
1005              } else {
1006                  $articles[] = ($thing) ? parse($thing) : parse_form($fname);
1007              }
1008  
1009              // Sending these to paging_link(); Required?
1010              $uPosted = $a['uPosted'];
1011  
1012              unset($GLOBALS['thisarticle']);
1013          }
1014  
1015          return doLabel($label, $labeltag).doWrap($articles, $wraptag, $break, $class);
1016      }
1017  }
1018  
1019  // -------------------------------------------------------------
1020  
1021  function doArticle($atts, $thing = null)
1022  {
1023      global $pretext, $prefs, $thisarticle;
1024      extract($prefs);
1025      extract($pretext);
1026  
1027      extract(gpsa(array(
1028          'parentid',
1029          'preview',
1030      )));
1031  
1032      $theAtts = lAtts(array(
1033          'allowoverride' => '1',
1034          'form'          => 'default',
1035          'status'        => STATUS_LIVE,
1036          'pgonly'        => 0,
1037      ), $atts, 0);
1038      extract($theAtts);
1039  
1040      // Save *all* atts to get hold of the current article filter criteria.
1041      filterAtts($atts);
1042  
1043      // No output required.
1044      if ($pgonly) {
1045          return '';
1046      }
1047  
1048      // If a form is specified, $thing is for doArticles() - hence ignore
1049      // $thing here.
1050      if (!empty($atts['form'])) {
1051          $thing = '';
1052      }
1053  
1054      if ($status) {
1055          $status = in_array(strtolower($status), array('sticky', STATUS_STICKY)) ? STATUS_STICKY : STATUS_LIVE;
1056      }
1057  
1058      if (empty($thisarticle) or $thisarticle['thisid'] != $id) {
1059          $id = assert_int($id);
1060          $thisarticle = null;
1061  
1062          $q_status = ($status ? "AND Status = ".intval($status) : "AND Status IN (".STATUS_LIVE.",".STATUS_STICKY.")");
1063  
1064          $rs = safe_row("*, UNIX_TIMESTAMP(Posted) AS uPosted, UNIX_TIMESTAMP(Expires) AS uExpires, UNIX_TIMESTAMP(LastMod) AS uLastMod",
1065              'textpattern',
1066              "ID = $id $q_status LIMIT 1");
1067  
1068          if ($rs) {
1069              extract($rs);
1070              populateArticleData($rs);
1071          }
1072      }
1073  
1074      if (!empty($thisarticle) and ($thisarticle['status'] == $status or gps('txpreview'))) {
1075          extract($thisarticle);
1076          $thisarticle['is_first'] = 1;
1077          $thisarticle['is_last'] = 1;
1078  
1079          if ($allowoverride and $override_form) {
1080              $article = parse_form($override_form);
1081          } else {
1082              $article = ($thing) ? parse($thing) : parse_form($form);
1083          }
1084  
1085          if ($use_comments and $comments_auto_append) {
1086              $article .= parse_form('comments_display');
1087          }
1088  
1089          unset($GLOBALS['thisarticle']);
1090  
1091          return $article;
1092      }
1093  }
1094  
1095  // -------------------------------------------------------------
1096  
1097  function article_custom($atts, $thing = null)
1098  {
1099      return parseArticles($atts, '1', $thing);
1100  }
1101  
1102  // -------------------------------------------------------------
1103  
1104  function parseArticles($atts, $iscustom = 0, $thing = null)
1105  {
1106      global $pretext, $is_article_list;
1107      $old_ial = $is_article_list;
1108      $is_article_list = empty($pretext['id']) || $iscustom;
1109      article_push();
1110      $r = ($is_article_list) ? doArticles($atts, $iscustom, $thing) : doArticle($atts, $thing);
1111      article_pop();
1112      $is_article_list = $old_ial;
1113  
1114      return $r;
1115  }
1116  
1117  // -------------------------------------------------------------
1118  
1119  function makeOut()
1120  {
1121      $array['status'] = '200';
1122  
1123      foreach (func_get_args() as $a) {
1124          $in = gps($a);
1125  
1126          if (is_scalar($in)) {
1127              $array[$a] = strval($in);
1128          } else {
1129              $array[$a] = '';
1130              $array['status'] = '404';
1131          }
1132      }
1133  
1134      return $array;
1135  }
1136  
1137  // -------------------------------------------------------------
1138  
1139  function validContext($context)
1140  {
1141      foreach (array('article', 'image', 'file', 'link') as $type) {
1142          $valid[gTxt($type.'_context')] = $type;
1143      }
1144  
1145      return isset($valid[$context]) ? $valid[$context] : 'article';
1146  }

title

Description

title

Description

title

Description

title

title

Body