Textpattern PHP Cross Reference Content Management Systems

Source: /textpattern/publish/taghandlers.php - 5501 lines - 156515 bytes - Summary - Text - Print

Description: Collection of tag functions.

   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   * Collection of tag functions.
  26   *
  27   * @package Tag
  28   */
  29  
  30  Txp::get('\Textpattern\Tag\Registry')
  31      ->register('page_title')
  32      ->register('css')
  33      ->register('image')
  34      ->register('thumbnail')
  35      ->register('output_form')
  36      ->register('txp_yield', 'yield')
  37      ->register('txp_if_yield', 'if_yield')
  38      ->register('feed_link')
  39      ->register('link_feed_link')
  40      ->register('linklist')
  41      ->register('tpt_link', 'link')
  42      ->register('linkdesctitle')
  43      ->register('link_name')
  44      ->register('link_url')
  45      ->register('link_author')
  46      ->register('link_description')
  47      ->register('link_date')
  48      ->register('link_category')
  49      ->register('link_id')
  50      ->register('if_first', 'if_first_link', 'link')
  51      ->register('if_last', 'if_last_link', 'link')
  52      ->register('email')
  53      ->register('password_protect')
  54      ->register('recent_articles')
  55      ->register('recent_comments')
  56      ->register('related_articles')
  57      ->register('popup')
  58      ->register('category_list')
  59      ->register('section_list')
  60      ->register('search_input')
  61      ->register('search_term')
  62      ->register('link_to', 'link_to_next')
  63      ->register('link_to', 'link_to_prev', 'prev')
  64      ->register('next_title')
  65      ->register('prev_title')
  66      ->register('site_name')
  67      ->register('site_slogan')
  68      ->register('link_to_home')
  69      ->register('txp_pager', 'newer', true)
  70      ->register('txp_pager', 'older', false)
  71      ->register('txp_pager', 'pages')
  72      ->register('text')
  73      ->register('article_id')
  74      ->register('article_url_title')
  75      ->register('if_article_id')
  76      ->register('posted')
  77      ->register('posted', 'modified', 'modified')
  78      ->register('posted', 'expires', 'expires')
  79      ->register('if_expires')
  80      ->register('if_expired')
  81      ->register('comments_count')
  82      ->register('comments_invite')
  83      ->register('comments_form')
  84      ->register('comments_error')
  85      ->register('if_comments_error')
  86      ->register('comments')
  87      ->register('comments_preview')
  88      ->register('if_comments_preview')
  89      ->register('comment_permlink')
  90      ->register('comment_id')
  91      ->register('comment_name')
  92      ->register('comment_email')
  93      ->register('comment_web')
  94      ->register('comment_time')
  95      ->register('comment_message')
  96      ->register('comment_anchor')
  97      ->register(array('\Textpattern\Tag\Syntax\Authors', 'renderAuthors'), 'authors')
  98      ->register('author')
  99      ->register('author_email')
 100      ->register('if_author')
 101      ->register('if_article_author')
 102      ->register('body')
 103      ->register('title')
 104      ->register('excerpt')
 105      ->register('article_category', 'category1')
 106      ->register('article_category', array('category2', array('number' => 2)))
 107      ->register('category')
 108      ->register('section')
 109      ->register('keywords')
 110      ->register('if_keywords')
 111      ->register('if_description')
 112      ->register('if_article_image')
 113      ->register('article_image')
 114      ->register('search_result_title')
 115      ->register('search_result_excerpt')
 116      ->register('search_result_url')
 117      ->register('search_result_date')
 118      ->register('search_result_count')
 119      ->register('image_index')
 120      ->register('image_display')
 121      ->register('images')
 122      ->register('image_info')
 123      ->register('image_url')
 124      ->register('image_author')
 125      ->register('image_date')
 126      ->register('if_first', 'if_first_image', 'image')
 127      ->register('if_last', 'if_last_image', 'image')
 128      ->register('if_thumbnail')
 129      ->register('if_comments')
 130      ->register('if_comments_allowed')
 131      ->register('if_comments_disallowed')
 132      ->register('if_individual_article')
 133      ->register('if_article_list')
 134      ->register('meta_keywords')
 135      ->register('meta_description')
 136      ->register('meta_author')
 137      ->register('permlink')
 138      ->register('lang')
 139      ->register('breadcrumb')
 140      ->register('if_excerpt')
 141      ->register('if_search')
 142      ->register('if_search_results')
 143      ->register('if_category')
 144      ->register('if_article_category')
 145      ->register('if_first', 'if_first_category', 'category')
 146      ->register('if_last', 'if_last_category', 'category')
 147      ->register('if_section')
 148      ->register('if_article_section')
 149      ->register('if_first', 'if_first_section', 'section')
 150      ->register('if_last', 'if_last_section', 'section')
 151      ->register('if_logged_in')
 152      ->register('if_request')
 153      ->register('php')
 154      ->register('txp_header', 'header')
 155      ->register('custom_field')
 156      ->register('if_custom_field')
 157      ->register('site_url')
 158      ->register('error_message')
 159      ->register('error_status')
 160      ->register('if_status')
 161      ->register('page_url')
 162      ->register('if_different')
 163      ->register('if_first', 'if_first_article')
 164      ->register('if_last', 'if_last_article')
 165      ->register('if_plugin')
 166      ->register('file_download_list')
 167      ->register('file_download')
 168      ->register('file_download_link')
 169      ->register('file_download_size')
 170      ->register('file_download_time', 'file_download_created')
 171      ->register('file_download_time', 'file_download_modified', 'modified')
 172      ->register('file_download_id')
 173      ->register('file_download_name')
 174      ->register('file_download_category')
 175      ->register('file_download_author')
 176      ->register('file_download_downloads')
 177      ->register('file_download_description')
 178      ->register('if_first', 'if_first_file', 'file')
 179      ->register('if_last', 'if_last_file', 'file')
 180      ->register('hide')
 181      ->register('rsd')
 182      ->register('variable')
 183      ->register('if_variable')
 184      ->register('article')
 185      ->register('article_custom')
 186      ->register('txp_die')
 187      ->register('txp_eval', 'evaluate')
 188      ->register('comments_help')
 189      ->register('comment_input', 'comment_name_input')
 190      ->register('comment_input', 'comment_email_input', 'email', 'clean_url')
 191      ->register('comment_input', array('comment_web_input', array('placeholder' => 'http(s)://')), 'web', 'clean_url')
 192      ->register('comment_message_input')
 193      ->register('comment_remember')
 194      ->register('comment_preview')
 195      ->register('comment_submit')
 196  // Global attributes (false just removes unknown attribute warning)
 197      ->registerAttr(false, 'class, html_id, labeltag')
 198      ->registerAttr(true, 'not, txp-process, breakby, breakclass, wrapform, evaluate')
 199      ->registerAttr('txp_escape', 'escape')
 200      ->registerAttr('txp_wraptag', 'wraptag, label, trim, replace, default');
 201  
 202  // -------------------------------------------------------------
 203  
 204  function page_title($atts)
 205  {
 206      global $parentid, $thisarticle, $q, $c, $author, $context, $s, $pg, $sitename;
 207  
 208      extract(lAtts(array('separator' => ' | '), $atts));
 209  
 210      $appending = txpspecialchars($separator.$sitename);
 211      $parent_id = (int) $parentid;
 212      $pageStr = ($pg ? $separator.gTxt('page').' '.$pg : '');
 213  
 214      if ($parent_id) {
 215          $out = gTxt('comments_on').' '.escape_title(safe_field("Title", 'textpattern', "ID = $parent_id")).$appending;
 216      } elseif (isset($thisarticle['title'])) {
 217          $out = escape_title($thisarticle['title']).$appending;
 218      } elseif ($q) {
 219          $out = gTxt('search_results').' '.gTxt('txt_quote_double_open').txpspecialchars($q).gTxt('txt_quote_double_close').$pageStr.$appending;
 220      } elseif ($c) {
 221          $out = txpspecialchars(fetch_category_title($c, $context)).$pageStr.$appending;
 222      } elseif ($s && $s != 'default') {
 223          $out = txpspecialchars(fetch_section_title($s)).$pageStr.$appending;
 224      } elseif ($author) {
 225          $out = txpspecialchars(get_author_name($author)).$pageStr.$appending;
 226      } elseif ($pg) {
 227          $out = gTxt('page').' '.$pg.$appending;
 228      } else {
 229          $out = txpspecialchars($sitename);
 230      }
 231  
 232      return $out;
 233  }
 234  
 235  // -------------------------------------------------------------
 236  
 237  function css($atts)
 238  {
 239      global $css, $doctype, $pretext;
 240  
 241      extract(lAtts(array(
 242          'format' => 'url',
 243          'media'  => 'screen',
 244          'name'   => $css,
 245          'rel'    => 'stylesheet',
 246          'theme'  => $pretext['skin'],
 247          'title'  => '',
 248      ), $atts));
 249  
 250      if (empty($name)) {
 251          $name = 'default';
 252      }
 253  
 254      $out = '';
 255      $format = strtolower(preg_replace('/\s+/', '', $format));
 256      list($mode, $format) = explode('.', $format.'.'.$format);
 257  
 258      if (has_handler('css.url')) {
 259          $url = callback_event('css.url', '', false, compact('name', 'theme'));
 260      } elseif ($mode === 'flat') {
 261          $url = array();
 262          $skin_dir = urlencode(get_pref('skin_dir'));
 263  
 264          foreach (do_list_unique($name) as $n) {
 265              $url[] = hu.$skin_dir.'/'.urlencode($theme).'/'.Txp::get('Textpattern\Skin\Css')->getDir().'/'.urlencode($n).'.css';
 266          }
 267      } else {
 268          $url = hu.'css.php?n='.urlencode($name).'&t='.urlencode($theme);
 269      }
 270  
 271      switch ($format) {
 272          case 'link':
 273              foreach ((array)$url as $href) {
 274                  $out .= tag_void('link', array(
 275                      'rel'   => $rel,
 276                      'type'  => $doctype != 'html5' ? 'text/css' : '',
 277                      'media' => $media,
 278                      'title' => $title,
 279                      'href'  => $href,
 280                  )).n;
 281              }
 282              break;
 283          default:
 284              $out .= txpspecialchars(is_array($url) ? implode(',', $url) : $url);
 285              break;
 286      }
 287  
 288      return $out;
 289  }
 290  
 291  // -------------------------------------------------------------
 292  
 293  function component($atts)
 294  {
 295      global $doctype, $pretext, $txp_context;
 296      static $mimetypes = null, $dir = null,
 297          $internals = array('id', 's', 'c', 'context', 'q', 'm', 'pg', 'p', 'month', 'author'),
 298          $defaults = array(
 299          'format'  => 'url',
 300          'form'    => '',
 301          'context' => null,
 302          'rel'     => '',
 303          'title'   => '',
 304      );
 305  
 306      if (!isset($mimetypes)) {
 307          $mimetypes = Txp::get('Textpattern\Skin\Form')->getMimeTypes();
 308          $dir = urlencode(Txp::get('Textpattern\Skin\Form')->getDir());
 309      }
 310  
 311      extract(lAtts($defaults, $atts, false));
 312  
 313      if (empty($form)) {
 314          return;
 315      }
 316  
 317      $format = strtolower(preg_replace('/\s+/', '', $format));
 318      list($mode, $format) = explode('.', $format.'.'.$format);
 319      $theme = urlencode($pretext['skin']);
 320      $out = '';
 321      $qs = get_context($context, $internals) + array_diff_key($atts, $defaults);
 322  
 323      if ($mode === 'flat') {
 324          $url = array();
 325          $skin_dir = urlencode(get_pref('skin_dir'));
 326  
 327          foreach (do_list_unique($form) as $n) {
 328              $type = pathinfo($n, PATHINFO_EXTENSION);
 329              if (isset($mimetypes[$type])) {
 330                  $url[] = hu.$skin_dir.'/'.$theme.'/'.$dir.'/'.urlencode($type).'/'.urlencode($n).($qs ? join_qs($qs) : '');
 331              } else {
 332                  $url[] = pagelinkurl(array('f' => $n) + $qs);
 333              }
 334          }
 335      } else {
 336          $url = pagelinkurl(array('f' => $form) + $qs);
 337      }
 338  
 339      switch ($format) {
 340          case 'url':
 341          case 'flat':
 342              $out .= is_array($url) ? implode(',', $url) : $url;
 343              break;
 344          case 'link':
 345              foreach ((array)$url as $href) {
 346                  $out .= tag_void('link', array(
 347                      'rel'   => $rel,
 348                      'title' => $title,
 349                      'href'  => $href,
 350                  )).n;
 351              }
 352              break;
 353          case 'script':
 354              foreach ((array)$url as $href) {
 355                  $out .= tag(null, 'script', array(
 356                      'title' => $title,
 357                      'type'  => $doctype != 'html5' ? 'application/javascript' : '',
 358                      'src'  => $href,
 359                  )).n;
 360              }
 361              break;
 362          case 'image':
 363              foreach ((array)$url as $href) {
 364                  $out .= tag_void('img', array(
 365                      'title' => $title,
 366                      'src'  => $href,
 367                  )).n;
 368              }
 369              break;
 370          default:
 371              foreach ((array)$url as $href) {
 372                  $out .= href($title ? $title : $href, $href, array(
 373                      'rel'   => $rel,
 374                  )).n;
 375              }
 376      }
 377  
 378      return $out;
 379  }
 380  
 381  // -------------------------------------------------------------
 382  
 383  function image($atts)
 384  {
 385      return thumbnail(array('thumbnail' => isset($atts['thumbnail']) ? $atts['thumbnail'] : false) + $atts);
 386  }
 387  
 388  // -------------------------------------------------------------
 389  
 390  function thumbnail($atts)
 391  {
 392      global $doctype;
 393  
 394      extract(lAtts(array(
 395          'escape'    => true,
 396          'title'     => '',
 397          'class'     => '',
 398          'html_id'   => '',
 399          'height'    => '',
 400          'id'        => '',
 401          'link'      => 0,
 402          'link_rel'  => '',
 403          'loading'   => null,
 404          'name'      => '',
 405          'poplink'   => 0, // Deprecated, 4.7
 406          'style'     => '',
 407          'wraptag'   => '',
 408          'width'     => '',
 409          'thumbnail' => null,
 410      ), $atts));
 411  
 412      if (isset($atts['poplink'])) {
 413          trigger_error(gTxt('deprecated_attribute', array('{name}' => 'poplink')), E_USER_NOTICE);
 414      }
 415  
 416      if ($imageData = imageFetchInfo($id, $name)) {
 417          $thumb_ = $thumbnail || !isset($thumbnail) ? 'thumb_' : '';
 418  
 419          if ($thumb_ && empty($imageData['thumbnail'])) {
 420              $thumb_ = '';
 421  
 422              if (!isset($thumbnail)) {
 423                  return;
 424              }
 425          }
 426  
 427          extract($imageData);
 428  
 429          if ($escape) {
 430              $alt = txp_escape(array('escape' => $escape), $alt);
 431          }
 432  
 433          if ($title === true) {
 434              $title = $caption;
 435          }
 436  
 437          if ($width == '' && ($thumb_ && $thumb_w || !$thumb_ && $w)) {
 438              $width = ${$thumb_.'w'};
 439          }
 440  
 441          if ($height == '' && ($thumb_ && $thumb_h || !$thumb_ && $h)) {
 442              $height = ${$thumb_.'h'};
 443          }
 444  
 445          $out = '<img src="'.imagesrcurl($id, $ext, !empty($thumb_)).
 446              '" alt="'.txpspecialchars($alt, ENT_QUOTES, 'UTF-8', false).'"';
 447  
 448          if ($title) {
 449              $out .= ' title="'.txpspecialchars($title, ENT_QUOTES, 'UTF-8', false).'"';
 450          }
 451  
 452          if ($html_id && !$wraptag) {
 453              $out .= ' id="'.txpspecialchars($html_id).'"';
 454          }
 455  
 456          if ($class && !$wraptag) {
 457              $out .= ' class="'.txpspecialchars($class).'"';
 458          }
 459  
 460          if ($style) {
 461              $out .= ' style="'.txpspecialchars($style).'"';
 462          }
 463  
 464          if ($width) {
 465              $out .= ' width="'.(int) $width.'"';
 466          }
 467  
 468          if ($height) {
 469              $out .= ' height="'.(int) $height.'"';
 470          }
 471  
 472          if ($loading && $doctype == 'html5' && in_array($loading, array('auto', 'eager', 'lazy'))) {
 473              $out .= ' loading="'.$loading.'"';
 474          }
 475  
 476          $out .= ' />';
 477  
 478          if ($link && $thumb_) {
 479              $attribs = '';
 480  
 481              if (!empty($link_rel)) {
 482                  $attribs .= " rel='".txpspecialchars($link_rel)."'";
 483              }
 484  
 485              $out = href($out, imagesrcurl($id, $ext), $attribs);
 486          } elseif ($poplink) {
 487              $out = '<a href="'.imagesrcurl($id, $ext).'"'.
 488                  ' onclick="window.open(this.href, \'popupwindow\', '.
 489                  '\'width='.$w.', height='.$h.', scrollbars, resizable\'); return false;">'.$out.'</a>';
 490          }
 491  
 492          return $wraptag ? doTag($out, $wraptag, $class, '', $html_id) : $out;
 493      }
 494  }
 495  
 496  // -------------------------------------------------------------
 497  
 498  function output_form($atts, $thing = null)
 499  {
 500      global $txp_atts, $yield, $txp_yield;
 501  
 502      if (empty($atts['form'])) {
 503          trigger_error(gTxt('form_not_specified'));
 504  
 505          return '';
 506      }
 507  
 508      $form = $atts['form'];
 509      $to_yield = isset($atts['yield']) ? $atts['yield'] : false;
 510      unset($atts['form'], $atts['yield'], $txp_atts['form'], $txp_atts['yield']);
 511  
 512      if ($form === true && empty($atts)) {
 513          return fetch_form(do_list_unique($to_yield));
 514      }
 515  
 516      if (!empty($to_yield)) {
 517          $to_yield = $to_yield === true ? $atts : array_fill_keys(do_list_unique($to_yield), null);
 518          empty($txp_atts) or $txp_atts = array_diff_key($txp_atts, $to_yield);
 519      }
 520  
 521      if (isset($atts['format'])) {// component
 522          empty($txp_atts) or $atts = array_diff_key($atts, $txp_atts);
 523  
 524          return component($atts + array('form' => $form));
 525      } elseif (is_array($to_yield)) {
 526          $atts = lAtts($to_yield, $atts) or $atts = array();
 527      }
 528  
 529      foreach ($atts as $name => $value) {
 530          if (!isset($txp_yield[$name])) {
 531              $txp_yield[$name] = array();
 532          }
 533  
 534          $txp_yield[$name][] = array($value, false);
 535      }
 536  
 537      $yield[] = $thing;
 538      $out = parse_form($form);
 539      array_pop($yield);
 540  
 541      foreach ($atts as $name => $value) {
 542          $result = array_pop($txp_yield[$name]);
 543  
 544          if (!empty($result[1])) {
 545              unset($txp_atts[$name]);
 546          }
 547      }
 548  
 549      return $out;
 550  }
 551  
 552  // -------------------------------------------------------------
 553  
 554  function txp_yield($atts, $thing = null)
 555  {
 556      global $yield, $txp_yield, $txp_atts, $txp_item;
 557  
 558      extract(lAtts(array(
 559          'name'    => '',
 560          'else'    => false,
 561          'default' => false,
 562          'item'    => null
 563      ), $atts));
 564  
 565      if (isset($item)) {
 566          $inner = isset($txp_item[$item]) ? $txp_item[$item] : null;
 567      } elseif ($name === '') {
 568          $end = empty($yield) ? null : end($yield);
 569  
 570          if (isset($end)) {
 571              $inner = parse($end, empty($else));
 572          }
 573      } elseif (!empty($txp_yield[$name])) {
 574          list($inner) = end($txp_yield[$name]);
 575          $txp_yield[$name][key($txp_yield[$name])][1] = true;
 576      }
 577  
 578      if (!isset($inner)) {
 579          $escape = isset($txp_atts['escape']) ? $txp_atts['escape'] : null;
 580          $inner = $default !== false ?
 581              ($default === true ? page_url(array('type' => $name, 'escape' => $escape)) : $default) :
 582              ($thing ? parse($thing) : $thing);
 583      }
 584  
 585      return $inner;
 586  }
 587  
 588  
 589  function txp_if_yield($atts, $thing = null)
 590  {
 591      global $yield, $txp_yield, $txp_item;
 592  
 593      extract(lAtts(array(
 594          'name'  => '',
 595          'else'  => false,
 596          'value' => null,
 597          'item'  => null
 598      ), $atts));
 599  
 600      if (isset($item)) {
 601          $inner = isset($txp_item[$item]) ? $txp_item[$item] : null;
 602      } elseif ($name === '') {
 603          $end = empty($yield) ? null : end($yield);
 604  
 605          if (isset($end)) {
 606              $inner = $value === null ? ($else ? getIfElse($end, false) : true) : parse($end, empty($else));
 607          }
 608      } elseif (empty($txp_yield[$name])) {
 609          $inner = null;
 610      } else {
 611          list($inner) = end($txp_yield[$name]);
 612      }
 613  
 614      return parse($thing, isset($inner) && ($value === null || (string)$inner === (string)$value || $inner && $value === true));
 615  }
 616  
 617  // -------------------------------------------------------------
 618  
 619  function feed_link($atts, $thing = null)
 620  {
 621      global $s, $c;
 622  
 623      extract(lAtts(array(
 624          'category' => $c,
 625          'flavor'   => 'rss',
 626          'format'   => 'a',
 627          'label'    => '',
 628          'limit'    => '',
 629          'section'  => ($s == 'default' ? '' : $s),
 630          'title'    => gTxt('rss_feed_title'),
 631      ), $atts));
 632  
 633      $url = pagelinkurl(array(
 634          $flavor    => '1',
 635          'section'  => $section,
 636          'category' => $category,
 637          'limit'    => $limit,
 638      ));
 639  
 640      if ($flavor == 'atom') {
 641          $title = ($title == gTxt('rss_feed_title')) ? gTxt('atom_feed_title') : $title;
 642      }
 643  
 644      $title = txpspecialchars($title);
 645  
 646      $type = ($flavor == 'atom') ? 'application/atom+xml' : 'application/rss+xml';
 647  
 648      if ($format == 'link') {
 649          return '<link rel="alternate" type="'.$type.'" title="'.$title.'" href="'.$url.'" />';
 650      }
 651  
 652      $txt = ($thing === null ? $label : parse($thing));
 653  
 654      $out = href($txt, $url, array(
 655          'type'  => $type,
 656          'title' => $title,
 657      ));
 658  
 659      return $out;
 660  }
 661  
 662  // -------------------------------------------------------------
 663  
 664  function link_feed_link($atts)
 665  {
 666      global $c;
 667  
 668      extract(lAtts(array(
 669          'category' => $c,
 670          'flavor'   => 'rss',
 671          'format'   => 'a',
 672          'label'    => '',
 673          'title'    => gTxt('rss_feed_title'),
 674          'wraptag'  => '',
 675          'class'    => __FUNCTION__,
 676      ), $atts));
 677  
 678      $url = pagelinkurl(array(
 679          $flavor    => '1',
 680          'area'     => 'link',
 681          'category' => $category,
 682      ));
 683  
 684      if ($flavor == 'atom') {
 685          $title = ($title == gTxt('rss_feed_title')) ? gTxt('atom_feed_title') : $title;
 686      }
 687  
 688      $title = txpspecialchars($title);
 689  
 690      $type = ($flavor == 'atom') ? 'application/atom+xml' : 'application/rss+xml';
 691  
 692      if ($format == 'link') {
 693          return '<link rel="alternate" type="'.$type.'" title="'.$title.'" href="'.$url.'" />';
 694      }
 695  
 696      $out = href($label, $url, array(
 697          'type'  => $type,
 698          'title' => $title,
 699      ));
 700  
 701      return ($wraptag) ? doTag($out, $wraptag, $class) : $out;
 702  }
 703  
 704  // -------------------------------------------------------------
 705  
 706  function linklist($atts, $thing = null)
 707  {
 708      global $s, $c, $context, $thislink, $thispage, $pretext;
 709  
 710      extract(lAtts(array(
 711          'break'       => '',
 712          'category'    => '',
 713          'author'      => '',
 714          'realname'    => '',
 715          'auto_detect' => 'category, author',
 716          'class'       => __FUNCTION__,
 717          'form'        => 'plainlinks',
 718          'id'          => '',
 719          'pageby'      => '',
 720          'limit'       => 0,
 721          'offset'      => 0,
 722          'month'       => '',
 723          'time'        => null,
 724          'sort'        => 'linksort asc',
 725          'wraptag'     => '',
 726      ), $atts));
 727  
 728      $where = array();
 729      $filters = isset($atts['category']) || isset($atts['author']) || isset($atts['realname']);
 730      $context_list = (empty($auto_detect) || $filters) ? array() : do_list_unique($auto_detect);
 731      $pageby = ($pageby == 'limit') ? $limit : $pageby;
 732  
 733      if ($category) {
 734          $where[] = "category IN ('".join("','", doSlash(do_list_unique($category)))."')";
 735      }
 736  
 737      if ($id) {
 738          $where[] = "id IN ('".join("','", doSlash(do_list_unique($id, array(',', '-'))))."')";
 739      }
 740  
 741      if ($author) {
 742          $where[] = "author IN ('".join("','", doSlash(do_list_unique($author)))."')";
 743      }
 744  
 745      if ($realname) {
 746          $authorlist = safe_column("name", 'txp_users', "RealName IN ('".join("','", doArray(doSlash(do_list_unique($realname)), 'urldecode'))."')");
 747          if ($authorlist) {
 748              $where[] = "author IN ('".join("','", doSlash($authorlist))."')";
 749          }
 750      }
 751  
 752      // If no links are selected, try...
 753      if (!$where && !$filters) {
 754          foreach ($context_list as $ctxt) {
 755              switch ($ctxt) {
 756                  case 'category':
 757                      // ...the global category in the URL.
 758                      if ($context == 'link' && !empty($c)) {
 759                          $where[] = "category = '".doSlash($c)."'";
 760                      }
 761                      break;
 762                  case 'author':
 763                      // ...the global author in the URL.
 764                      if ($context == 'link' && !empty($pretext['author'])) {
 765                          $where[] = "author = '".doSlash($pretext['author'])."'";
 766                      }
 767                      break;
 768              }
 769  
 770              // Only one context can be processed.
 771              if ($where) {
 772                  break;
 773              }
 774          }
 775      }
 776  
 777      if (!$where && $filters) {
 778          // If nothing matches, output nothing.
 779          return '';
 780      }
 781  
 782      if ($time === null || $time || $month) {
 783          $where[] = buildTimeSql($month, $time === null ? 'past' : $time, 'date');
 784      }
 785  
 786      if (!$where) {
 787          // If nothing matches, start with all links.
 788          $where[] = "1 = 1";
 789      }
 790  
 791      $where = join(" AND ", $where);
 792  
 793      // Set up paging if required.
 794      if ($limit && $pageby) {
 795          $pg = (!$pretext['pg']) ? 1 : $pretext['pg'];
 796          $pgoffset = $offset + (($pg - 1) * $pageby);
 797  
 798          if (empty($thispage)) {
 799              $grand_total = safe_count('txp_link', $where);
 800              $total = $grand_total - $offset;
 801              $numPages = ($pageby > 0) ? ceil($total/$pageby) : 1;
 802  
 803              // Send paging info to txp:newer and txp:older.
 804              $pageout['pg']          = $pg;
 805              $pageout['numPages']    = $numPages;
 806              $pageout['s']           = $s;
 807              $pageout['c']           = $c;
 808              $pageout['context']     = 'link';
 809              $pageout['grand_total'] = $grand_total;
 810              $pageout['total']       = $total;
 811              $thispage = $pageout;
 812          }
 813      } else {
 814          $pgoffset = $offset;
 815      }
 816  
 817      $qparts = array(
 818          $where,
 819          'ORDER BY '.sanitizeForSort($sort),
 820          ($limit) ? 'LIMIT '.intval($pgoffset).', '.intval($limit) : '',
 821      );
 822  
 823      $rs = safe_rows_start("*, UNIX_TIMESTAMP(date) AS uDate", 'txp_link', join(' ', $qparts));
 824  
 825      if ($rs) {
 826          $count = 0;
 827          $last = numRows($rs);
 828          $out = array();
 829  
 830          while ($a = nextRow($rs)) {
 831              ++$count;
 832              $thislink = $a;
 833              $thislink['date'] = $thislink['uDate'];
 834              $thislink['is_first'] = ($count == 1);
 835              $thislink['is_last'] = ($count == $last);
 836              unset($thislink['uDate']);
 837  
 838              $out[] = ($thing) ? parse($thing) : parse_form($form);
 839  
 840              $thislink = '';
 841          }
 842  
 843          if ($out) {
 844              return doWrap($out, $wraptag, $break, $class);
 845          }
 846      }
 847  
 848      return '';
 849  }
 850  
 851  // -------------------------------------------------------------
 852  
 853  // NOTE: tpt_ prefix used because link() is a PHP function. See publish.php.
 854  function tpt_link($atts)
 855  {
 856      global $thislink;
 857  
 858      extract(lAtts(array(
 859          'rel'    => '',
 860          'id'     => '',
 861          'name'   => '',
 862          'escape' => true,
 863      ), $atts));
 864  
 865      $rs = $thislink;
 866      $sql = array();
 867  
 868      if ($id) {
 869          $sql[] = "id = ".intval($id);
 870      } elseif ($name) {
 871          $sql[] = "linkname = '".doSlash($name)."'";
 872      }
 873  
 874      if ($sql) {
 875          $rs = safe_row("linkname, url", 'txp_link', implode(" AND ", $sql)." LIMIT 1");
 876      }
 877  
 878      if (!$rs) {
 879          trigger_error(gTxt('unknown_link'));
 880  
 881          return '';
 882      }
 883  
 884      return tag(
 885          $escape ? txp_escape(array('escape' => $escape), $rs['linkname']) : $rs['linkname'], 'a',
 886          ($rel ? ' rel="'.txpspecialchars($rel).'"' : '').
 887          ' href="'.txpspecialchars($rs['url']).'"'
 888      );
 889  }
 890  
 891  // -------------------------------------------------------------
 892  
 893  function linkdesctitle($atts)
 894  {
 895      global $thislink;
 896  
 897      assert_link();
 898  
 899      extract(lAtts(array('rel' => '', 'escape' => true), $atts));
 900  
 901      $description = ($thislink['description'])
 902          ? ' title="'.txpspecialchars($thislink['description']).'"'
 903          : '';
 904  
 905      return tag(
 906          $escape ? txp_escape(array('escape' => $escape), $thislink['linkname']) : $thislink['linkname'], 'a',
 907          ($rel ? ' rel="'.txpspecialchars($rel).'"' : '').
 908          ' href="'.doSpecial($thislink['url']).'"'.$description
 909      );
 910  }
 911  
 912  // -------------------------------------------------------------
 913  
 914  function link_name($atts)
 915  {
 916      global $thislink;
 917  
 918      assert_link();
 919  
 920      extract(lAtts(array('escape' => null), $atts));
 921  
 922      return ($escape === null)
 923          ? txpspecialchars($thislink['linkname'])
 924          : $thislink['linkname'];
 925  }
 926  
 927  // -------------------------------------------------------------
 928  
 929  function link_url()
 930  {
 931      global $thislink;
 932  
 933      assert_link();
 934  
 935      return doSpecial($thislink['url']);
 936  }
 937  
 938  // -------------------------------------------------------------
 939  
 940  function link_author($atts)
 941  {
 942      global $thislink, $s;
 943  
 944      assert_link();
 945  
 946      extract(lAtts(array(
 947          'link'         => 0,
 948          'title'        => 1,
 949          'section'      => '',
 950          'this_section' => '',
 951      ), $atts));
 952  
 953      if ($thislink['author']) {
 954          $author_name = get_author_name($thislink['author']);
 955          $display_name = txpspecialchars(($title) ? $author_name : $thislink['author']);
 956  
 957          $section = ($this_section) ? ($s == 'default' ? '' : $s) : $section;
 958  
 959          $author = ($link)
 960              ? href($display_name, pagelinkurl(array(
 961                  's'       => $section,
 962                  'author'  => $author_name,
 963                  'context' => 'link',
 964              )))
 965              : $display_name;
 966  
 967          return $author;
 968      }
 969  }
 970  
 971  // -------------------------------------------------------------
 972  
 973  function link_description($atts)
 974  {
 975      global $thislink;
 976  
 977      assert_link();
 978  
 979      extract(lAtts(array('escape' => null), $atts));
 980  
 981      if ($thislink['description']) {
 982          return ($escape === null) ?
 983              txpspecialchars($thislink['description']) :
 984              $thislink['description'];
 985      }
 986  }
 987  
 988  // -------------------------------------------------------------
 989  
 990  function link_date($atts)
 991  {
 992      global $thislink, $dateformat;
 993  
 994      assert_link();
 995  
 996      extract(lAtts(array(
 997          'format' => $dateformat,
 998          'gmt'    => '',
 999          'lang'   => '',
1000      ), $atts));
1001  
1002      return safe_strftime($format, $thislink['date'], $gmt, $lang);
1003  }
1004  
1005  // -------------------------------------------------------------
1006  
1007  function link_category($atts)
1008  {
1009      global $thislink;
1010  
1011      assert_link();
1012  
1013      extract(lAtts(array('title' => 0), $atts));
1014  
1015      if ($thislink['category']) {
1016          $category = ($title)
1017              ? fetch_category_title($thislink['category'], 'link')
1018              : $thislink['category'];
1019  
1020          return $category;
1021      }
1022  }
1023  
1024  // -------------------------------------------------------------
1025  
1026  function link_id()
1027  {
1028      global $thislink;
1029  
1030      assert_link();
1031  
1032      return $thislink['id'];
1033  }
1034  
1035  // -------------------------------------------------------------
1036  
1037  function email($atts, $thing = null)
1038  {
1039      extract(lAtts(array(
1040          'email'    => '',
1041          'linktext' => gTxt('contact'),
1042          'title'    => '',
1043      ), $atts));
1044  
1045      if ($email) {
1046          if ($thing !== null) {
1047              $linktext = parse($thing);
1048          }
1049  
1050          // Obfuscate link text?
1051          if (is_valid_email($linktext)) {
1052              $linktext = Txp::get('\Textpattern\Mail\Encode')->entityObfuscateAddress($linktext);
1053          }
1054  
1055          return href(
1056              $linktext,
1057              Txp::get('\Textpattern\Mail\Encode')->entityObfuscateAddress('mailto:'.$email),
1058              ($title ? ' title="'.txpspecialchars($title).'"' : '')
1059          );
1060      }
1061  
1062      return '';
1063  }
1064  
1065  // -------------------------------------------------------------
1066  
1067  function password_protect($atts, $thing = null)
1068  {
1069      ob_start();
1070  
1071      extract(lAtts(array(
1072          'login' => null,
1073          'pass'  => null,
1074          'privs' => null,
1075      ), $atts));
1076  
1077      if ($pass === null) {
1078          $access = ($user = is_logged_in($login)) !== false && ($privs === null || in_list($user['privs'], $privs));
1079      } else {
1080          $au = serverSet('PHP_AUTH_USER');
1081          $ap = serverSet('PHP_AUTH_PW');
1082  
1083          // For PHP as (f)cgi, two rules in htaccess often allow this workaround.
1084          $ru = serverSet('REDIRECT_REMOTE_USER');
1085  
1086          if (!$au && !$ap && strpos($ru, 'Basic') === 0) {
1087              list($au, $ap) = explode(':', base64_decode(substr($ru, 6)));
1088          }
1089  
1090          $access = $au === $login && $ap === $pass;
1091      }
1092  
1093      if ($access === false && $pass !== null) {
1094          header('WWW-Authenticate: Basic realm="Private"');
1095      }
1096  
1097      if ($thing === null) {
1098          if ($access === false) {
1099              txp_die(gTxt('auth_required'), '401');
1100          }
1101  
1102          return '';
1103      }
1104  
1105      return parse($thing, $access);
1106  }
1107  
1108  // -------------------------------------------------------------
1109  
1110  function recent_articles($atts, $thing = null)
1111  {
1112      global $prefs;
1113  
1114      $atts = lAtts(array(
1115          'break'    => 'br',
1116          'category' => '',
1117          'class'    => __FUNCTION__,
1118          'form'     => '',
1119          'label'    => gTxt('recent_articles'),
1120          'labeltag' => '',
1121          'limit'    => 10,
1122          'offset'   => 0,
1123          'section'  => '',
1124          'sort'     => 'Posted DESC',
1125          'wraptag'  => '',
1126          'no_widow' => '',
1127      ), $atts);
1128  
1129      if (!isset($thing) && !$atts['form']) {
1130          $thing = '<txp:permlink><txp:title no_widow="'.($atts['no_widow'] ? '1' : '').'" /></txp:permlink>';
1131      }
1132  
1133      unset($atts['no_widow']);
1134  
1135      return article_custom($atts, $thing);
1136  }
1137  
1138  // -------------------------------------------------------------
1139  
1140  function recent_comments($atts, $thing = null)
1141  {
1142      global $prefs;
1143      global $thisarticle, $thiscomment;
1144  
1145      extract(lAtts(array(
1146          'break'    => br,
1147          'class'    => __FUNCTION__,
1148          'form'     => '',
1149          'limit'    => 10,
1150          'offset'   => 0,
1151          'sort'     => 'posted DESC',
1152          'wraptag'  => '',
1153      ), $atts));
1154  
1155      $sort = preg_replace('/\bposted\b/', 'd.posted', $sort);
1156      $expired = ($prefs['publish_expired_articles']) ? '' : " AND (".now('expires')." <= t.Expires OR t.Expires IS NULL) ";
1157  
1158      $rs = startRows("SELECT d.name, d.email, d.web, d.message, d.discussid, UNIX_TIMESTAMP(d.Posted) AS time, t.ID AS thisid,
1159              UNIX_TIMESTAMP(t.Posted) AS posted, t.Title AS title, t.Section AS section, t.Category1, t.Category2, t.url_title
1160          FROM ".safe_pfx('txp_discuss')." AS d INNER JOIN ".safe_pfx('textpattern')." AS t ON d.parentid = t.ID
1161          WHERE t.Status >= ".STATUS_LIVE.$expired." AND d.visible = ".VISIBLE."
1162          ORDER BY ".sanitizeForSort($sort)."
1163          LIMIT ".intval($offset).", ".intval($limit));
1164  
1165      if ($rs) {
1166          $out = array();
1167          $old_article = $thisarticle;
1168  
1169          while ($c = nextRow($rs)) {
1170              if ($form === '' && $thing === null) {
1171                  $out[] = href(
1172                      txpspecialchars($c['name']).' ('.escape_title($c['title']).')',
1173                      permlinkurl($c).'#c'.$c['discussid']
1174                  );
1175              } else {
1176                  $thiscomment['name'] = $c['name'];
1177                  $thiscomment['email'] = $c['email'];
1178                  $thiscomment['web'] = $c['web'];
1179                  $thiscomment['message'] = $c['message'];
1180                  $thiscomment['discussid'] = $c['discussid'];
1181                  $thiscomment['time'] = $c['time'];
1182  
1183                  // Allow permlink guesstimation in permlinkurl(), elsewhere.
1184                  $thisarticle['thisid'] = $c['thisid'];
1185                  $thisarticle['posted'] = $c['posted'];
1186                  $thisarticle['title'] = $c['title'];
1187                  $thisarticle['section'] = $c['section'];
1188                  $thisarticle['url_title'] = $c['url_title'];
1189  
1190                  if ($thing === null && $form !== '') {
1191                      $out[] = parse_form($form);
1192                  } else {
1193                      $out[] = parse($thing);
1194                  }
1195              }
1196          }
1197  
1198          if ($out) {
1199              unset($GLOBALS['thiscomment']);
1200              $thisarticle = $old_article;
1201  
1202              return doWrap($out, $wraptag, $break, $class);
1203          }
1204      }
1205  
1206      return '';
1207  }
1208  
1209  // -------------------------------------------------------------
1210  
1211  function related_articles($atts, $thing = null)
1212  {
1213      global $thisarticle, $prefs;
1214  
1215      assert_article();
1216  
1217      $atts = lAtts(array(
1218          'break'    => br,
1219          'class'    => __FUNCTION__,
1220          'form'     => '',
1221          'limit'    => 10,
1222          'offset'   => 0,
1223          'match'    => 'Category',
1224          'no_widow' => '',
1225          'section'  => '',
1226          'sort'     => 'Posted DESC',
1227          'wraptag'  => '',
1228      ), $atts);
1229  
1230      $match = array_intersect(do_list_unique(strtolower($atts['match'])), array_merge(array('category', 'category1', 'category2', 'author', 'keywords'), getCustomFields()));
1231      $categories = $cats = array();
1232  
1233      foreach ($match as $cf) {
1234          switch ($cf) {
1235              case 'category':
1236              case 'category1':
1237              case 'category2':
1238                  foreach(($cf == 'category' ? array('category1', 'category2') : array($cf)) as $cat) {
1239                      if (!empty($thisarticle[$cat])) {
1240                          $cats[] = $thisarticle[$cat];
1241                      }
1242                  }
1243  
1244                  $categories[] = ucwords($cf);
1245                  break;
1246              case 'author':
1247                  $atts['author'] = $thisarticle['authorid'];
1248                  break;
1249              default:
1250                  if (empty($thisarticle[$cf])) {
1251                      return '';
1252                  }
1253  
1254                  $atts[$cf] = $thisarticle[$cf];
1255                  break;
1256          }
1257      }
1258  
1259      if (!empty($cats)) {
1260          $atts['category'] = implode(',', $cats);
1261      } elseif ($categories) {
1262          return '';
1263      }
1264  
1265      $atts['match'] = implode(',', $categories);
1266      $atts['exclude'] = $thisarticle['thisid'];
1267  
1268      if ($atts['form'] === '' && $thing === null) {
1269          $thing = '<txp:permlink><txp:title no_widow="'.($atts['no_widow'] ? '1' : '').'" /></txp:permlink>';
1270      }
1271  
1272      unset($atts['no_widow']);
1273  
1274      return article_custom($atts, $thing);
1275  }
1276  
1277  // -------------------------------------------------------------
1278  
1279  function popup($atts)
1280  {
1281      global $s, $c, $permlink_mode;
1282  
1283      extract(lAtts(array(
1284          'label'        => gTxt('browse'),
1285          'wraptag'      => '',
1286          'class'        => '',
1287          'section'      => '',
1288          'this_section' => 0,
1289          'type'         => 'category',
1290      ), $atts));
1291  
1292      $type = substr($type, 0, 1);
1293  
1294      if ($type == 's') {
1295          $rs = safe_rows_start("name, title", 'txp_section', "name != 'default' ORDER BY name");
1296      } else {
1297          $rs = safe_rows_start("name, title", 'txp_category', "type = 'article' AND name != 'root' ORDER BY name");
1298      }
1299  
1300      if ($rs) {
1301          $out = array();
1302  
1303          $current = ($type == 's') ? $s : $c;
1304  
1305          $sel = '';
1306          $selected = false;
1307  
1308          while ($a = nextRow($rs)) {
1309              extract($a);
1310  
1311              if ($name == $current) {
1312                  $sel = ' selected="selected"';
1313                  $selected = true;
1314              }
1315  
1316              $out[] = '<option value="'.$name.'"'.$sel.'>'.txpspecialchars($title).'</option>';
1317  
1318              $sel = '';
1319          }
1320  
1321          if ($out) {
1322              $section = ($this_section) ? ($s == 'default' ? '' : $s) : $section;
1323  
1324              $out = n.'<select name="'.txpspecialchars($type).'" onchange="submit(this.form);">'.
1325                  n.t.'<option value=""'.($selected ? '' : ' selected="selected"').'>&#160;</option>'.
1326                  n.t.join(n.t, $out).
1327                  n.'</select>';
1328  
1329              if ($label) {
1330                  $out = $label.br.$out;
1331              }
1332  
1333              if ($wraptag) {
1334                  $out = doTag($out, $wraptag, $class);
1335              }
1336  
1337              if (($type == 's' || $permlink_mode == 'messy')) {
1338                  $action = hu;
1339                  $his = ($section !== '') ? n.hInput('s', $section) : '';
1340              } else {
1341                  // Clean URLs for category popup.
1342                  $action = pagelinkurl(array('s' => $section));
1343                  $his = '';
1344              }
1345  
1346              return '<form method="get" action="'.$action.'">'.
1347                  '<div>'.
1348                  $his.
1349                  n.$out.
1350                  n.'<noscript><div><input type="submit" value="'.gTxt('go').'" /></div></noscript>'.
1351                  n.'</div>'.
1352                  n.'</form>';
1353          }
1354      }
1355  }
1356  
1357  // -------------------------------------------------------------
1358  
1359  // Output href list of site categories.
1360  function category_list($atts, $thing = null)
1361  {
1362      global $s, $c, $thiscategory;
1363      static $cache = array(), $level = 0;
1364  
1365      extract(lAtts(array(
1366          'active_class' => '',
1367          'break'        => br,
1368          'categories'   => '',
1369          'class'        => __FUNCTION__,
1370          'exclude'      => '',
1371          'form'         => '',
1372          'html_id'      => '',
1373          'label'        => '',
1374          'labeltag'     => '',
1375          'parent'       => '',
1376          'section'      => '',
1377          'children'     => 1,
1378          'sort'         => empty($atts['categories']) ? 'name ASC' : '',
1379          'this_section' => 0,
1380          'type'         => 'article',
1381          'wraptag'      => '',
1382          'limit'        => '',
1383          'offset'       => '',
1384      ), $atts));
1385  
1386      $categories = $categories === true ?
1387          array(isset($thiscategory['name']) ? $thiscategory['name'] : ($c ? $c : 'root')) :
1388          do_list_unique($categories);
1389  
1390      if (isset($atts['categories']) && empty($categories)) {
1391          return '';
1392      }
1393  
1394      $roots = ($parent === true ? array(isset($thiscategory['name']) ? $thiscategory['name'] : ($c ? $c : 'root')) : do_list_unique($parent)) or $roots = $categories or $roots = array('root');
1395      $level++;
1396      $section = ($this_section) ? ($s == 'default' ? '' : $s) : $section;
1397      $multiple = count($roots) > 1;
1398      $root = implode(',', $roots);
1399      $children = $children === true ? PHP_INT_MAX : intval(is_numeric($children) ? $children : !empty($children));
1400      $sql_query = "type = '".doSlash($type)."'".($sort ? ' order by '.sanitizeForSort($sort) : ($categories ? " order by FIELD(name, ".implode(',', quote_list($categories)).")": ''));
1401      $sql_limit = $limit !== '' || $offset ? "LIMIT ".intval($offset).", ".($limit === '' || $limit === true ? PHP_INT_MAX : intval($limit)) : '';
1402      $exclude = $exclude ? ($exclude === true ? $roots : do_list_unique($exclude)) : array();
1403      $sql_exclude = $exclude && $sql_limit ? " and name not in(".implode(',', quote_list($exclude)).")" : '';
1404      $nocache = !$children || $sql_limit || $children == $level;
1405      $hash = md5($nocache ? uniqid() : $sql_query);
1406  
1407      if (!isset($cache[$hash])) {
1408          $cache[$hash] = array();
1409      }
1410  
1411      if (!isset($cache[$hash][$root]) || !$multiple && $root != 'root' && empty($cache[$hash][$root][$root])) {
1412          $cache[$hash][$root] = array();
1413  
1414          if (!$children || !in_array('root', $roots)) {
1415              $cats = safe_rows('name, parent, title, description, lft, rgt', 'txp_category', "name IN (".implode(',', quote_list($roots)).") and $sql_query") or $cats = array();
1416              $retrieve = false;
1417              $between = array();
1418  
1419              foreach ($cats as $cat) {
1420                  extract($cat);
1421                  $name = doSlash($name);
1422                  $between[] = $children ? "lft>=$lft and rgt<=$rgt" : "name='$name' or parent='$name'";
1423  
1424                  if ($rgt - $lft > 1) {
1425                      $retrieve = true;
1426                  }
1427              }
1428  
1429              $cats = $retrieve ? safe_rows('name, parent, title, description', 'txp_category', "name!='root' $sql_exclude and (".implode(' or ', $between).") and $sql_query $sql_limit") : $cats;
1430          } else {
1431              $cats = safe_rows('name, parent, title, description', 'txp_category', "name !='root' $sql_exclude and $sql_query $sql_limit");
1432          }
1433  
1434          foreach ($cats as $cat) {
1435              extract($cat);
1436              $node = $children == $level ? $root : $name;
1437  
1438              if (!isset($cache[$hash][$node])) {
1439                  $cache[$hash][$node] = array();
1440              }
1441  
1442              $cache[$hash][$node][$name] = $cat;
1443  
1444              if ($children != $level) {
1445                  if ($multiple && in_array($name, $roots)) {
1446                      $cache[$hash][$root][$name] = $cat;
1447                  }
1448  
1449                  if (!isset($cache[$hash][$parent])) {
1450                      $cache[$hash][$parent] = array();
1451                  }
1452  
1453                  $cache[$hash][$parent][$name] = $cat;
1454  
1455                  if ($multiple && in_array($parent, $roots)) {
1456                      $cache[$hash][$root][$name] = $cat;
1457                  }
1458              }
1459          }
1460      }
1461  
1462      $oldcategory = isset($thiscategory) ? $thiscategory : null;
1463      $out = array();
1464      $count = 0;
1465      $last = count($cache[$hash][$root]);
1466  
1467      foreach ($cache[$hash][$root] as $name => $thiscategory) {
1468          if (!in_array($name, $exclude) && (!$categories || in_array($name, $categories))) {
1469              $count++;
1470  
1471              if (!isset($thing) && !$form) {
1472                  extract($thiscategory);
1473                  $out[] = tag(txpspecialchars($title), 'a',
1474                      (($active_class && (0 == strcasecmp($c, $name))) ? ' class="'.txpspecialchars($active_class).'"' : '').
1475                      ' href="'.pagelinkurl(array(
1476                          's'       => $section,
1477                          'c'       => $name,
1478                          'context' => $type,
1479                      )).'"'
1480                  ).(
1481                      isset($cache[$hash][$name]) && $children > $level && count($cache[$hash][$name]) > 1
1482                      ? category_list(array(
1483                          'parent'  => $name,
1484                          'exclude' => ($exclude ? implode(',', $exclude).',' : '').$name,
1485                          'label'   => '',
1486                          'html_id' => '',
1487                      ) + $atts)
1488                      : ''
1489                  );
1490              } else {
1491                  $thiscategory['type'] = $type;
1492                  $thiscategory['is_first'] = ($count == 1);
1493                  $thiscategory['is_last'] = ($count == $last);
1494  
1495                  if (isset($atts['section'])) {
1496                      $thiscategory['section'] = $section;
1497                  }
1498  
1499                  $out[] = $form ? parse_form($form) : parse($thing);
1500              }
1501          } else {
1502              $last--;
1503          }
1504      }
1505  
1506      $thiscategory = $oldcategory;
1507      $level--;
1508  
1509      if ($nocache || $level <= 0) {
1510          unset($cache[$hash]);
1511      }
1512  
1513      return $out ? ($label ? doLabel($label, $labeltag) : '').doWrap($out, $wraptag, compact('break', 'class', 'html_id')) : '';
1514  }
1515  
1516  // -------------------------------------------------------------
1517  
1518  // Output href list of site sections.
1519  function section_list($atts, $thing = null)
1520  {
1521      global $sitename, $s, $thissection;
1522  
1523      extract(lAtts(array(
1524          'active_class'    => '',
1525          'break'           => br,
1526          'class'           => __FUNCTION__,
1527          'default_title'   => $sitename,
1528          'exclude'         => '',
1529          'form'            => '',
1530          'html_id'         => '',
1531          'include_default' => '',
1532          'sections'        => '',
1533          'sort'            => '',
1534          'wraptag'         => '',
1535          'offset'          => '',
1536          'limit'           => '',
1537      ), $atts));
1538  
1539      $sql_limit = '';
1540      $sql_sort = sanitizeForSort($sort);
1541      $sql = array();
1542  
1543      if ($limit !== '' || $offset) {
1544          $sql_limit = " LIMIT ".intval($offset).", ".($limit === '' ? PHP_INT_MAX : intval($limit));
1545      }
1546  
1547  
1548      if ($sections === true) {
1549          $sql[] = '1';
1550      } elseif ($sections) {
1551          if ($include_default) {
1552              $sections .= ', default';
1553          }
1554  
1555          $sections = join(',', quote_list(do_list_unique($sections)));
1556          $sql[] = "name IN ($sections)";
1557  
1558          if (!$sql_sort) {
1559              $sql_sort = "FIELD(name, $sections)";
1560          }
1561      } else {
1562          $sql[] = '1'.filterFrontPage('name', 'page');
1563      }
1564  
1565      if ($exclude === true) {
1566          $sql[] = "searchable";
1567      } elseif ($exclude) {
1568          $exclude = join(',', quote_list(do_list_unique($exclude)));
1569          $sql[] = "name NOT IN ($exclude)";
1570      }
1571  
1572      if (!$include_default) {
1573          $sql[] = "name != 'default'";
1574      }
1575  
1576      if (!$sql_sort) {
1577          $sql_sort = "name ASC";
1578      }
1579  
1580      if ($include_default) {
1581          $sql_sort = "name != 'default', ".$sql_sort;
1582      }
1583  
1584      $rs = safe_rows_start(
1585          "name, title, description",
1586          'txp_section',
1587          join(" AND ", $sql)." ORDER BY ".$sql_sort.$sql_limit
1588      );
1589  
1590      if ($rs && $last = numRows($rs)) {
1591          $out = array();
1592          $count = 0;
1593  
1594          if (isset($thissection)) {
1595              $old_section = $thissection;
1596          }
1597  
1598          while ($a = nextRow($rs)) {
1599              ++$count;
1600              extract($a);
1601  
1602              if ($name == 'default') {
1603                  $title = $default_title;
1604              }
1605  
1606              if ($form === '' && $thing === null) {
1607                  $url = pagelinkurl(array('s' => $name));
1608  
1609                  $out[] = tag(txpspecialchars($title), 'a',
1610                      (($active_class && (0 == strcasecmp($s, $name))) ? ' class="'.txpspecialchars($active_class).'"' : '').
1611                      ' href="'.$url.'"'
1612                  );
1613              } else {
1614                  $thissection = array(
1615                      'name'        => $name,
1616                      'title'       => $title,
1617                      'description' => $description,
1618                      'is_first'    => ($count == 1),
1619                      'is_last'     => ($count == $last),
1620                  );
1621  
1622                  if ($thing === null && $form !== '') {
1623                      $out[] = parse_form($form);
1624                  } else {
1625                      $out[] = parse($thing);
1626                  }
1627              }
1628          }
1629  
1630          $thissection = isset($old_section) ? $old_section : null;
1631  
1632          if ($out) {
1633              return doWrap($out, $wraptag, compact('break', 'class', 'html_id'));
1634          }
1635      }
1636  
1637      return '';
1638  }
1639  
1640  // -------------------------------------------------------------
1641  
1642  // Input form for search queries.
1643  function search_input($atts, $thing = null)
1644  {
1645      global $q, $permlink_mode, $doctype;
1646      static $outside = null;
1647  
1648      $inside = is_array($outside);
1649  
1650      extract(lAtts(array(
1651          'form'        => null,
1652          'wraptag'     => 'p',
1653          'class'       => __FUNCTION__,
1654          'size'        => '15',
1655          'html_id'     => '',
1656          'label'       => gTxt('search'),
1657          'aria_label'  => '',
1658          'placeholder' => '',
1659          'button'      => '',
1660          'section'     => '',
1661          'match'       => 'exact',
1662      ), $inside ? $atts + $outside : $atts));
1663  
1664      unset($atts['form']);
1665  
1666      if (!$inside && !isset($form) && !isset($thing) && empty($atts)) {
1667          $form = 'search_input';
1668      }
1669  
1670      if ($form && $form = fetch_form($form)) {
1671          $thing = $form;
1672      }
1673  
1674      if (isset($thing)) {
1675          $oldatts = $outside;
1676          $outside = $atts;
1677          $out = parse($thing);
1678          $outside = $oldatts;
1679      } else {
1680          $h5 = ($doctype == 'html5');
1681          $out = fInput(
1682              $h5 ? 'search' : 'text',
1683              array(
1684                  'name'        => 'q',
1685                  'aria-label'  => $aria_label,
1686                  'placeholder' => $placeholder,
1687                  'required'    => $h5,
1688                  'size'        => $size,
1689                  'class'       => $wraptag || empty($atts['class']) ? false : $class
1690              ),
1691              $q
1692          );
1693      }
1694  
1695      if ($form || $inside) {
1696          empty($atts['wraptag']) or $out = doTag($out, $wraptag, $class);
1697  
1698          return $out;
1699      }
1700  
1701      $sub = (!empty($button)) ? '<input type="submit" value="'.txpspecialchars($button).'" />' : '';
1702      $id =  (!empty($html_id)) ? ' id="'.txpspecialchars($html_id).'"' : '';
1703  
1704      $out = (!empty($label)) ? txpspecialchars($label).br.$out.$sub : $out.$sub;
1705      $out = ($match === 'exact') ? $out : hInput('m', txpspecialchars($match)).$out;
1706      $out = ($wraptag) ? doTag($out, $wraptag, $class) : $out;
1707  
1708      if (!$section) {
1709          return '<form role="search" method="get" action="'.hu.'"'.$id.'>'.
1710              n.$out.
1711              n.'</form>';
1712      }
1713  
1714      if ($permlink_mode != 'messy') {
1715          return '<form role="search" method="get" action="'.pagelinkurl(array('s' => $section)).'"'.$id.'>'.
1716              n.$out.
1717              n.'</form>';
1718      }
1719  
1720      return '<form role="search" method="get" action="'.hu.'"'.$id.'>'.
1721          n.hInput('s', $section).
1722          n.$out.
1723          n.'</form>';
1724  }
1725  
1726  // -------------------------------------------------------------
1727  
1728  function search_term($atts)
1729  {
1730      global $q;
1731  
1732      if (empty($q)) {
1733          return '';
1734      }
1735  
1736      return txpspecialchars($q);
1737  }
1738  
1739  // -------------------------------------------------------------
1740  
1741  // Link to next/prev article, if it exists.
1742  function link_to($atts, $thing = null, $target = 'next')
1743  {
1744      global $thisarticle, $txp_context;
1745      static $lAtts = array(
1746          'form'       => '',
1747          'link'       => 1,
1748          'showalways' => 0
1749      );
1750  
1751      if (!in_array($target, array('next', 'prev')) || !assert_article()) {
1752          return '';
1753      }
1754  
1755      $atts += array('context' => empty($txp_context) ? true : null);
1756      extract($atts + $lAtts, EXTR_SKIP);
1757  
1758      if (is_array($thisarticle)) {
1759          if (!isset($thisarticle[$target])) {
1760              $thisarticle = $thisarticle + getNextPrev();
1761          }
1762  
1763          if ($thisarticle[$target] !== false) {
1764              $oldarticle = $thisarticle;
1765              $thisarticle = $thisarticle[$target];
1766              $url = permlink(array_diff_key($atts, $lAtts));
1767  
1768              if ($form || $thing !== null) {
1769                  populateArticleData($thisarticle);
1770                  $thisarticle['is_first'] = $thisarticle['is_last'] = true;
1771                  $thing = $form ? parse_form($form) : parse($thing);
1772                  $target_title = escape_title($thisarticle['Title']);
1773  
1774                  $url = $link ? href(
1775                      $thing,
1776                      $url,
1777                      ($target_title != $thing ? ' title="'.$target_title.'"' : '').
1778                      ' rel="'.$target.'"'
1779                  ) : $thing;
1780              }
1781  
1782              $thisarticle = $oldarticle;
1783  
1784              return $url;
1785          }
1786      }
1787  
1788      return ($showalways) ? parse($thing) : '';
1789  }
1790  
1791  // -------------------------------------------------------------
1792  
1793  function next_title()
1794  {
1795      global $thisarticle, $is_article_list;
1796  
1797      if (!assert_article()) {
1798          return $is_article_list ? '' : null;
1799      }
1800  
1801      if (!isset($thisarticle['next'])) {
1802          $thisarticle = $thisarticle + getNextPrev();
1803      }
1804  
1805      if ($thisarticle['next'] !== false) {
1806          return escape_title($thisarticle['next']['Title']);
1807      } else {
1808          return '';
1809      }
1810  }
1811  
1812  // -------------------------------------------------------------
1813  
1814  function prev_title()
1815  {
1816      global $thisarticle, $is_article_list;
1817  
1818      if (!assert_article()) {
1819          return $is_article_list ? '' : null;
1820      }
1821  
1822      if (!isset($thisarticle['prev'])) {
1823          $thisarticle = $thisarticle + getNextPrev();
1824      }
1825  
1826      if ($thisarticle['prev'] !== false) {
1827          return escape_title($thisarticle['prev']['Title']);
1828      } else {
1829          return '';
1830      }
1831  }
1832  
1833  // -------------------------------------------------------------
1834  
1835  function site_name()
1836  {
1837      global $sitename;
1838  
1839      return txpspecialchars($sitename);
1840  }
1841  
1842  // -------------------------------------------------------------
1843  
1844  function site_slogan()
1845  {
1846      global $site_slogan;
1847  
1848      return txpspecialchars($site_slogan);
1849  }
1850  
1851  // -------------------------------------------------------------
1852  
1853  function link_to_home($atts, $thing = null)
1854  {
1855      extract(lAtts(array('class' => false), $atts));
1856  
1857      if ($thing) {
1858          $class = ($class) ? ' class="'.txpspecialchars($class).'"' : '';
1859  
1860          return href(
1861              parse($thing),
1862              hu,
1863              $class.
1864              ' rel="home"'
1865          );
1866      }
1867  
1868      return hu;
1869  }
1870  
1871  // -------------------------------------------------------------
1872  
1873  function txp_pager($atts, $thing = null, $newer = null)
1874  {
1875      global $thispage, $is_article_list, $txp_context, $txp_item;
1876      static $pg = true, $numPages = null, $linkall = false, $top = 1, $shown = array();
1877      static $items = array('page' => null, 'total' => null, 'url' => null);
1878  
1879      $get = isset($atts['total']) && $atts['total'] === true;
1880      $set = $newer === null && (isset($atts['pg']) || isset($atts['total']) && !$get);
1881      $oldPages = $numPages;
1882      $oldpg = $pg;
1883  
1884      extract(lAtts($set ? array(
1885          'pg'         => $pg,
1886          'total'      => $numPages,
1887          'shift'      => 1,
1888          'showalways' => true,
1889          'link'       => false,
1890          ) : array(
1891          'showalways' => false,
1892          'title'      => '',
1893          'link'       => $linkall,
1894          'escape'     => 'html',
1895          'rel'        => '',
1896          'shift'      => false,
1897          'limit'      => 0,
1898          'break'      => '',) +
1899          ($get ? array(
1900          'total'      => true,
1901          ) : array()), $atts));
1902  
1903      if ($set) {
1904          if (isset($total) && $total !== true) {
1905              $numPages = (int)$total;
1906          } elseif ($pg === true) {
1907              $numPages = isset($thispage['numPages']) ? (int)$thispage['numPages'] : null;
1908          }
1909      }
1910  
1911      if (!isset($numPages)) {
1912          if (isset($thispage['numPages'])) {
1913              $numPages = (int)$thispage['numPages'];
1914          } else {
1915              return $is_article_list ? postpone_process() : '';
1916          }
1917      }
1918  
1919      if ($set) {
1920          $oldtop = $top;
1921          $top = $shift === true ? 0 : ((int)$shift < 0 ? $numPages + $shift + 1 : $shift);
1922          $oldshown = $shown;
1923          $oldlink = $linkall;
1924          $linkall = $link;
1925          $shown = array();
1926  
1927          if ($thing !== null) {
1928              $thing = $numPages >= ($showalways ? (int)$showalways : 2) ? parse($thing) : '';
1929              $numPages = $oldPages;
1930              $pg = $oldpg;
1931              $top = $oldtop;
1932              $linkall = $oldlink;
1933              $shown = $oldshown;
1934          }
1935  
1936          return $thing;
1937      }
1938  
1939      $pgc = $pg === true ? 'pg' : $pg;
1940      $thispg = $pg === true && isset($thispage['pg']) ? $thispage['pg'] : intval(gps($pgc, $top));
1941      $thepg = max(1, min($thispg, $numPages));
1942  
1943      if ($get) {
1944          if ($thing === null && $shift === false) {
1945              return $newer === null ? $numPages : ($newer ? $thepg - 1 : $numPages - $thepg);
1946          } elseif ($shift === true || $shift === false) {
1947              if ($newer !== null) {
1948                  $range = $newer ? $thepg - 1 : $numPages - $thepg;
1949              }
1950          } else {
1951              $range = (int)$shift;
1952          }
1953      }
1954  
1955      if (isset($range)) {
1956          if (!$range) {
1957              $pages = array();
1958          } elseif ($range > 0) {
1959              $pages = $newer === null ? range(-max($range, 2*$range + $thepg - $numPages), max($range, 2*$range - $thepg + 1)) :
1960                  range($newer ? max($range, 2*$range + $thepg - $numPages) : 1, $newer ? 1 : max($range, 2*$range - $thepg + 1));
1961          } else {
1962              $pages = $newer !== null ? ($newer ? range(-1, -max(-$range, -2*$range + $thepg - $numPages)) : range(-max(-$range, -2*$range - $thepg + 1), -1)) :
1963                  range(min(max(1 - $range - $thepg, 1 - 2*$range - $numPages), 0), max(0, min($numPages + $range - $thepg, $numPages + 2*$range - 1)));
1964          }
1965      } elseif (is_bool($shift)) {
1966          $pages = $newer === null ? ($shift ? range(1 - $thepg, $numPages - $thepg) : array(0)) : array($shift ? true : 1);
1967          $range = !$shift;
1968      } else {
1969          $pages = array_map('intval', do_list($shift, array(',', '-')));
1970          $range = false;
1971      }
1972  
1973      foreach ($items as $item => $val) {
1974          $items[$item] = isset($txp_item[$item]) ? $txp_item[$item] : null;
1975      }
1976  
1977      $txp_item['total'] = $numPages;
1978      $limit = $limit ? (int)$limit : -1;
1979      $old_context = $txp_context;
1980      $txp_context += get_context();
1981      $out = array();
1982  
1983      foreach ($pages as $page) {
1984          if ($newer === null) {
1985              $nextpg = $thepg + $page;
1986          } elseif ($newer) {
1987              $nextpg = $page === true ? 1 : ((int)$page < 0 ? -$page : $thepg - $page);
1988          } else {
1989              $nextpg = $page === true ? $numPages : ((int)$page < 0 ? $numPages + $page + 1 : $thepg + $page);
1990          }
1991  
1992          if (
1993              $nextpg >= ($newer === false && $range !== false ? $thepg + 1 : 1) &&
1994              $nextpg <= ($newer === true && $range !== false ? $thepg - 1 : $numPages)
1995          ) {
1996              if (empty($shown[$nextpg]) || $showalways) {
1997                  $txp_context[$pgc] = $nextpg == $top ? null : $nextpg;
1998                  $url = pagelinkurl($txp_context);
1999                  $txp_item['page'] = $nextpg;
2000                  $txp_item['url'] = $url;
2001  
2002                  if ($shift !== false || $newer === null || !is_bool($range)) {
2003                      $shown[$nextpg] = true;
2004                      $limit--;
2005                  }
2006  
2007                  if (isset($thing)) {
2008                      if ($escape == 'html') {
2009                          $title = escape_title($title);
2010                      } elseif ($escape) {
2011                          $title = txp_escape(array('escape' => $escape), $title);
2012                      }
2013  
2014                      $url = $link || $link === false && $nextpg != $thispg ? href(
2015                          parse($thing),
2016                          $url,
2017                          (empty($title) ? '' : ' title="'.$title.'"').
2018                          (empty($rel) ? '' : ' rel="'.txpspecialchars($rel).'"')
2019                      ) : parse($thing);
2020                  }
2021              } else {
2022                  $url = false;
2023              }
2024          } else {
2025              $url = isset($thing) ? parse($thing, false) : false;
2026          }
2027  
2028          empty($url) or $out[] = $url;
2029  
2030          if (!$limit) {
2031              break;
2032          }
2033      }
2034  
2035      foreach ($items as $item => $val) {
2036          $txp_item[$item] = $val;
2037      }
2038  
2039      $txp_context = $old_context;
2040  
2041      return doWrap($out, '', $break);
2042  }
2043  
2044  // -------------------------------------------------------------
2045  
2046  function text($atts)
2047  {
2048      extract(lAtts(array(
2049          'item'   => '',
2050          'escape' => 'html',
2051      ), $atts, false));
2052  
2053      if (!$item) {
2054          return '';
2055      }
2056  
2057      unset(
2058          $atts['item'],
2059          $atts['escape']
2060      );
2061  
2062      $tags = array();
2063  
2064      foreach ($atts as $name => $value) {
2065          $tags['{'.$name.'}'] = $value;
2066      }
2067  
2068      return gTxt($item, $tags, $escape);
2069  }
2070  
2071  // -------------------------------------------------------------
2072  
2073  function article_id()
2074  {
2075      global $thisarticle;
2076  
2077      assert_article();
2078  
2079      return $thisarticle['thisid'];
2080  }
2081  
2082  // -------------------------------------------------------------
2083  
2084  function article_url_title()
2085  {
2086      global $thisarticle;
2087  
2088      assert_article();
2089  
2090      return $thisarticle['url_title'];
2091  }
2092  
2093  // -------------------------------------------------------------
2094  
2095  function if_article_id($atts, $thing = null)
2096  {
2097      global $thisarticle, $pretext;
2098  
2099      assert_article();
2100  
2101      extract(lAtts(array('id' => $pretext['id']), $atts));
2102  
2103      $x = $id && in_list($thisarticle['thisid'], $id);
2104      return isset($thing) ? parse($thing, $x) : $x;
2105  }
2106  
2107  // -------------------------------------------------------------
2108  
2109  function posted($atts, $thing = null, $time = 'posted')
2110  {
2111      global $thisarticle, $id, $c, $pg, $dateformat, $archive_dateformat;
2112  
2113      assert_article();
2114  
2115      if (empty($thisarticle[$time])) {
2116          return '';
2117      }
2118  
2119      extract(lAtts(array(
2120          'format'  => '',
2121          'gmt'     => '',
2122          'lang'    => '',
2123      ), $atts));
2124  
2125      if ($format) {
2126          $out = safe_strftime($format, $thisarticle[$time], $gmt, $lang);
2127      } else {
2128          if ($id || $c || $pg) {
2129              $out = safe_strftime($archive_dateformat, $thisarticle[$time], $gmt, $lang);
2130          } else {
2131              $out = safe_strftime($dateformat, $thisarticle[$time], $gmt, $lang);
2132          }
2133      }
2134  
2135      return $out;
2136  }
2137  
2138  // -------------------------------------------------------------
2139  
2140  function if_expires($atts, $thing = null)
2141  {
2142      global $thisarticle;
2143  
2144      assert_article();
2145  
2146      $x = !empty($thisarticle['expires']);
2147      return isset($thing) ? parse($thing, $x) : $x;
2148  }
2149  
2150  // -------------------------------------------------------------
2151  
2152  function if_expired($atts, $thing = null)
2153  {
2154      global $thisarticle;
2155  
2156      assert_article();
2157  
2158      $x = !empty($thisarticle['expires']) && ($thisarticle['expires'] <= time());
2159      return isset($thing) ? parse($thing, $x) : $x;
2160  }
2161  
2162  // -------------------------------------------------------------
2163  
2164  function comments_count()
2165  {
2166      global $thisarticle;
2167  
2168      assert_article();
2169  
2170      return $thisarticle['comments_count'];
2171  }
2172  
2173  // -------------------------------------------------------------
2174  
2175  function comments_invite($atts)
2176  {
2177      global $thisarticle, $is_article_list;
2178  
2179      assert_article();
2180  
2181      extract($thisarticle);
2182      global $comments_mode;
2183  
2184      if (!$comments_invite) {
2185          $comments_invite = get_pref('comments_default_invite');
2186      }
2187  
2188      extract(lAtts(array(
2189          'class'      => __FUNCTION__,
2190          'showcount'  => true,
2191          'textonly'   => false,
2192          'showalways' => false,  // FIXME in crockery. This is only for BC.
2193          'wraptag'    => '',
2194      ), $atts));
2195  
2196      $invite_return = '';
2197  
2198      if (($annotate || $comments_count) && ($showalways || $is_article_list)) {
2199          $comments_invite = txpspecialchars($comments_invite);
2200          $ccount = ($comments_count && $showcount) ?  ' ['.$comments_count.']' : '';
2201  
2202          if ($textonly) {
2203              $invite_return = $comments_invite.$ccount;
2204          } else {
2205              if (!$comments_mode) {
2206                  $invite_return = doTag($comments_invite, 'a', $class, ' href="'.permlinkurl($thisarticle).'#'.gTxt('comment').'" ').$ccount;
2207              } else {
2208                  $invite_return = "<a href=\"".hu."?parentid=$thisid\" onclick=\"window.open(this.href, 'popupwindow', 'width=500,height=500,scrollbars,resizable,status'); return false;\"".(($class) ? ' class="'.txpspecialchars($class).'"' : '').'>'.$comments_invite.'</a> '.$ccount;
2209              }
2210          }
2211  
2212          if ($wraptag) {
2213              $invite_return = doTag($invite_return, $wraptag, $class);
2214          }
2215      }
2216  
2217      return $invite_return;
2218  }
2219  
2220  // -------------------------------------------------------------
2221  
2222  function popup_comments($atts, $thing = null)
2223  {
2224      extract(lAtts(array('form' => 'comments_display'), $atts));
2225  
2226      $rs = safe_row(
2227          "*, UNIX_TIMESTAMP(Posted) AS uPosted, UNIX_TIMESTAMP(LastMod) AS uLastMod, UNIX_TIMESTAMP(Expires) AS uExpires",
2228          'textpattern',
2229          "ID=".intval(gps('parentid'))." AND Status >= 4"
2230      );
2231  
2232      if ($rs) {
2233          populateArticleData($rs);
2234  
2235          return ($thing === null ? parse_form($form) : parse($thing));
2236      }
2237  
2238      return '';
2239  }
2240  
2241  // -------------------------------------------------------------
2242  
2243  function comments_form($atts, $thing = null)
2244  {
2245      global $thisarticle, $has_comments_preview;
2246      global $thiscommentsform; // TODO: Remove any uses of $thiscommentsform when removing deprecated attributes from below.
2247  
2248      // deprecated attributes since TXP 4.6. Most of these (except msgstyle)
2249      // were moved to the tags that occur within a comments_form, although
2250      // some of the names changed.
2251      $deprecated = array('isize', 'msgrows', 'msgcols', 'msgstyle',
2252          'previewlabel', 'submitlabel', 'rememberlabel', 'forgetlabel');
2253  
2254      foreach ($deprecated as $att) {
2255          if (isset($atts[$att])) {
2256              trigger_error(gTxt('deprecated_attribute', array('{name}' => $att)), E_USER_NOTICE);
2257          }
2258      }
2259  
2260      $atts = lAtts(array(
2261          'class'         => __FUNCTION__,
2262          'form'          => 'comment_form',
2263          'isize'         => '25',
2264          'msgcols'       => '25',
2265          'msgrows'       => '5',
2266          'msgstyle'      => '',
2267          'show_preview'  => empty($has_comments_preview),
2268          'wraptag'       => '',
2269          'previewlabel'  => gTxt('preview'),
2270          'submitlabel'   => gTxt('submit'),
2271          'rememberlabel' => gTxt('remember'),
2272          'forgetlabel'   => gTxt('forget'),
2273      ), $atts);
2274  
2275      extract($atts);
2276  
2277      $thiscommentsform = array_intersect_key($atts, array_flip($deprecated));
2278  
2279      assert_article();
2280  
2281      extract($thisarticle);
2282  
2283      $out = '';
2284      $ip = serverSet('REMOTE_ADDR');
2285      $blacklisted = is_blacklisted($ip);
2286  
2287      if (!checkCommentsAllowed($thisid)) {
2288          $out = graf(gTxt('comments_closed'), ' id="comments_closed"');
2289      } elseif ($blacklisted) {
2290          $out = graf(gTxt('your_ip_is_blacklisted_by'.' '.$blacklisted), ' id="comments_blocklisted"');
2291      } elseif (gps('commented') !== '') {
2292          $out = gTxt('comment_posted');
2293  
2294          if (gps('commented') === '0') {
2295              $out .= " ".gTxt('comment_moderated');
2296          }
2297  
2298          $out = graf($out, ' id="txpCommentInputForm"');
2299      } else {
2300          // Display a comment preview if required.
2301          if (ps('preview') && $show_preview) {
2302              $out = comments_preview(array());
2303          }
2304  
2305          extract(doDeEnt(psa(array('parentid', 'backpage'))));
2306  
2307          // If the form fields are filled (anything other than blank), pages really
2308          // should not be saved by a public cache (rfc2616/14.9.1).
2309          if (pcs('name') || pcs('email') || pcs('web')) {
2310              header('Cache-Control: private');
2311          }
2312  
2313          $url = $GLOBALS['pretext']['request_uri'];
2314  
2315          // Experimental clean URLs with only 404-error-document on Apache possibly
2316          // requires messy URLs for POST requests.
2317          if (defined('PARTLY_MESSY') && (PARTLY_MESSY)) {
2318              $url = hu.'?id='.intval($parentid);
2319          }
2320  
2321          $out .= '<form id="txpCommentInputForm" method="post" action="'.txpspecialchars($url).'#cpreview">'.
2322              n.'<div class="comments-wrapper">'.n. // Prevent XHTML Strict validation gotchas.
2323              ($thing === null ? parse_form($form) : parse($thing)).
2324              n.hInput('parentid', ($parentid ? $parentid : $thisid)).
2325              n.hInput('backpage', (ps('preview') ? $backpage : $url)).
2326              n.'</div>'.
2327              n.'</form>';
2328      }
2329  
2330      return (!$wraptag ? $out : doTag($out, $wraptag, $class));
2331  }
2332  
2333  // -------------------------------------------------------------
2334  
2335  function comment_input($atts, $thing = null, $field = 'name', $clean = false)
2336  {
2337      global $prefs, $thiscommentsform;
2338  
2339      extract(lAtts(array(
2340          'class'       => '',
2341          'size'        => $thiscommentsform['isize'],
2342          'aria_label'  => '',
2343          'placeholder' => '',
2344      ), $atts));
2345  
2346      $warn = false;
2347      $val = is_callable($clean) ? $clean(pcs($field)) : pcs($field);
2348      $h5 = ($prefs['doctype'] == 'html5');
2349      $required = get_pref('comments_require_'.$field);
2350  
2351      if (!empty($class)) {
2352          $class = ' '.txpspecialchars($class);
2353      }
2354  
2355      if (ps('preview')) {
2356          $comment = getComment();
2357          $val = $comment[$field];
2358          $warn = $required && !$val;
2359      }
2360  
2361      return fInput('text', array(
2362              'name'         => $field,
2363              'aria-label'   => $aria_label,
2364              'autocomplete' => $field == 'web' ? 'url' : $field,
2365              'placeholder'  => $placeholder,
2366              'required'     => $h5 && $required
2367          ), $val, 'comment_'.$field.'_input'.$class.($warn ? ' comments_error' : ''), '', '', $size, '', $field);
2368  }
2369  
2370  // -------------------------------------------------------------
2371  
2372  function comment_message_input($atts)
2373  {
2374      global $prefs, $thiscommentsform;
2375  
2376      extract(lAtts(array(
2377          'class'       => '',
2378          'rows'        => $thiscommentsform['msgrows'],
2379          'cols'        => $thiscommentsform['msgcols'],
2380          'aria_label'  => '',
2381          'placeholder' => ''
2382      ), $atts));
2383  
2384      $style = $thiscommentsform['msgstyle'];
2385      $commentwarn = false;
2386      $n_message = 'message';
2387      $formnonce = '';
2388      $message = '';
2389  
2390      if (!empty($class)) {
2391          $class = ' '.txpspecialchars($class);
2392      }
2393  
2394      if (ps('preview')) {
2395          $comment = getComment();
2396          $message = $comment['message'];
2397          $split = rand(1, 31);
2398          $nonce = getNextNonce();
2399          $secret = getNextSecret();
2400          safe_insert('txp_discuss_nonce', "issue_time = NOW(), nonce = '".doSlash($nonce)."', secret = '".doSlash($secret)."'");
2401          $n_message = md5('message'.$secret);
2402          $formnonce = n.hInput(substr($nonce, 0, $split), substr($nonce, $split));
2403          $commentwarn = (!trim($message));
2404      }
2405  
2406      $attr = join_atts(array(
2407          'cols'        => intval($cols),
2408          'rows'        => intval($rows),
2409          'required'    => $prefs['doctype'] == 'html5',
2410          'style'       => $style,
2411          'aria-label'  => $aria_label,
2412          'placeholder' => $placeholder
2413      ));
2414  
2415      return '<textarea class="txpCommentInputMessage'.$class.(($commentwarn) ? ' comments_error"' : '"').
2416          ' id="message" name="'.$n_message.'"'.$attr.
2417          '>'.txpspecialchars(substr(trim($message), 0, 65535)).'</textarea>'.
2418          callback_event('comment.form').
2419          $formnonce;
2420  }
2421  
2422  // -------------------------------------------------------------
2423  
2424  function comment_remember($atts)
2425  {
2426      global $thiscommentsform;
2427  
2428      extract(lAtts(array(
2429          'class'         => '',
2430          'rememberlabel' => $thiscommentsform['rememberlabel'],
2431          'forgetlabel'   => $thiscommentsform['forgetlabel']
2432      ), $atts));
2433  
2434      if (!empty($class)) {
2435          $class = ' class="'.txpspecialchars($class).'"';
2436      }
2437  
2438      extract(doDeEnt(psa(array('checkbox_type', 'remember', 'forget'))));
2439  
2440      if (!ps('preview')) {
2441          $rememberCookie = cs('txp_remember');
2442  
2443          if (!$rememberCookie) {
2444              $checkbox_type = 'remember';
2445          } else {
2446              $checkbox_type = 'forget';
2447          }
2448  
2449          // Inhibit default remember.
2450          if ($forget == 1 || (string) $rememberCookie === '0') {
2451              destroyCookies();
2452          }
2453      }
2454  
2455      if ($checkbox_type == 'forget') {
2456          $checkbox = checkbox('forget', 1, $forget, '', 'forget').' '.tag(txpspecialchars($forgetlabel), 'label', ' for="forget"'.$class);
2457      } else {
2458          $checkbox = checkbox('remember', 1, $remember, '', 'remember').' '.tag(txpspecialchars($rememberlabel), 'label', ' for="remember"'.$class);
2459      }
2460  
2461      $checkbox .= ' '.hInput('checkbox_type', $checkbox_type);
2462  
2463      return $checkbox;
2464  }
2465  
2466  // -------------------------------------------------------------
2467  
2468  function comment_preview($atts)
2469  {
2470      global $thiscommentsform;
2471  
2472      extract(lAtts(array(
2473          'class' => '',
2474          'label' => $thiscommentsform['previewlabel']
2475      ), $atts));
2476  
2477      if (!empty($class)) {
2478          $class = ' '.txpspecialchars($class);
2479      }
2480  
2481      return fInput('submit', 'preview', $label, 'button'.$class, '', '', '', '', 'txpCommentPreview', false);
2482  }
2483  
2484  // -------------------------------------------------------------
2485  
2486  function comment_submit($atts)
2487  {
2488      global $thiscommentsform;
2489  
2490      extract(lAtts(array(
2491          'class' => '',
2492          'label' => $thiscommentsform['submitlabel']
2493      ), $atts));
2494  
2495      if (!empty($class)) {
2496          $class = ' '.txpspecialchars($class);
2497      }
2498  
2499      // If all fields check out, the submit button is active/clickable.
2500      if (ps('preview')) {
2501          return fInput('submit', 'submit', $label, 'button'.$class, '', '', '', '', 'txpCommentSubmit', false);
2502      } else {
2503          return fInput('submit', 'submit', $label, 'button disabled'.$class, '', '', '', '', 'txpCommentSubmit', true);
2504      }
2505  }
2506  
2507  // -------------------------------------------------------------
2508  
2509  function comments_error($atts)
2510  {
2511      extract(lAtts(array(
2512          'break'   => 'br',
2513          'class'   => __FUNCTION__,
2514          'wraptag' => 'div',
2515      ), $atts));
2516  
2517      $evaluator = & get_comment_evaluator();
2518  
2519      $errors = $evaluator->get_result_message();
2520  
2521      if ($errors) {
2522          return doWrap($errors, $wraptag, $break, $class);
2523      }
2524  }
2525  
2526  // -------------------------------------------------------------
2527  
2528  function if_comments_error($atts, $thing = null)
2529  {
2530      $evaluator = & get_comment_evaluator();
2531  
2532      $x = (count($evaluator->get_result_message()) > 0);
2533      return isset($thing) ? parse($thing, $x) : $x;
2534  }
2535  
2536  // -------------------------------------------------------------
2537  
2538  function comments($atts, $thing = null)
2539  {
2540      global $thisarticle, $prefs;
2541      extract($prefs);
2542  
2543      extract(lAtts(array(
2544          'form'    => 'comments',
2545          'wraptag' => ($comments_are_ol ? 'ol' : ''),
2546          'break'   => ($comments_are_ol ? 'li' : 'div'),
2547          'class'   => __FUNCTION__,
2548          'limit'   => 0,
2549          'offset'  => 0,
2550          'sort'    => 'posted ASC',
2551      ), $atts));
2552  
2553      assert_article();
2554      extract($thisarticle);
2555  
2556      if (!$comments_count) {
2557          return '';
2558      }
2559  
2560      $qparts = array(
2561          "parentid = ".intval($thisid)." AND visible = ".VISIBLE,
2562          "ORDER BY ".sanitizeForSort($sort),
2563          ($limit) ? "LIMIT ".intval($offset).", ".intval($limit) : '',
2564      );
2565  
2566      $rs = safe_rows_start("*, UNIX_TIMESTAMP(posted) AS time", 'txp_discuss', join(' ', $qparts));
2567  
2568      $out = '';
2569  
2570      if ($rs) {
2571          $comments = array();
2572  
2573          while ($vars = nextRow($rs)) {
2574              $GLOBALS['thiscomment'] = $vars;
2575              $comments[] = ($thing === null ? parse_form($form) : parse($thing)).n;
2576              unset($GLOBALS['thiscomment']);
2577          }
2578  
2579          $out .= doWrap($comments, $wraptag, $break, $class);
2580      }
2581  
2582      return $out;
2583  }
2584  
2585  // -------------------------------------------------------------
2586  
2587  function comments_preview($atts, $thing = null)
2588  {
2589      global $has_comments_preview;
2590  
2591      if (!ps('preview')) {
2592          return '';
2593      }
2594  
2595      extract(lAtts(array(
2596          'form'    => 'comments',
2597          'wraptag' => '',
2598          'class'   => __FUNCTION__,
2599      ), $atts));
2600  
2601      assert_article();
2602  
2603      $preview = psa(array('name', 'email', 'web', 'message', 'parentid', 'remember'));
2604      $preview['time'] = time();
2605      $preview['discussid'] = 0;
2606      $preview['name'] = strip_tags($preview['name']);
2607      $preview['email'] = clean_url($preview['email']);
2608  
2609      if ($preview['message'] == '') {
2610          $in = getComment();
2611          $preview['message'] = $in['message'];
2612      }
2613  
2614      // It is called 'message', not 'novel'!
2615      $preview['message'] = markup_comment(substr(trim($preview['message']), 0, 65535));
2616  
2617      $preview['web'] = clean_url($preview['web']);
2618  
2619      $GLOBALS['thiscomment'] = $preview;
2620      $comments = ($thing === null ? parse_form($form) : parse($thing)).n;
2621      unset($GLOBALS['thiscomment']);
2622      $out = doTag($comments, $wraptag, $class);
2623  
2624      // Set a flag to tell the comments_form tag that it doesn't have to show
2625      // a preview.
2626      $has_comments_preview = true;
2627  
2628      return $out;
2629  }
2630  
2631  // -------------------------------------------------------------
2632  
2633  function if_comments_preview($atts, $thing = null)
2634  {
2635      $x = ps('preview') && checkCommentsAllowed(gps('parentid'));
2636      return isset($thing) ? parse($thing, $x) : $x;
2637  }
2638  
2639  // -------------------------------------------------------------
2640  
2641  function comment_permlink($atts, $thing)
2642  {
2643      global $thisarticle, $thiscomment;
2644  
2645      assert_article();
2646      assert_comment();
2647  
2648      extract($thiscomment);
2649      extract(lAtts(array('anchor' => empty($thiscomment['has_anchor_tag'])), $atts));
2650  
2651      $dlink = permlinkurl($thisarticle).'#c'.$discussid;
2652  
2653      $thing = parse($thing);
2654  
2655      $name = ($anchor ? ' id="c'.$discussid.'"' : '');
2656  
2657      return tag($thing, 'a', ' href="'.$dlink.'"'.$name);
2658  }
2659  
2660  // -------------------------------------------------------------
2661  
2662  function comment_id()
2663  {
2664      global $thiscomment;
2665  
2666      assert_comment();
2667  
2668      return $thiscomment['discussid'];
2669  }
2670  
2671  // -------------------------------------------------------------
2672  
2673  function comment_name($atts)
2674  {
2675      global $thiscomment, $prefs;
2676      static $encoder = null;
2677  
2678      assert_comment();
2679      isset($encoder) or $encoder = Txp::get('\Textpattern\Mail\Encode');
2680  
2681      extract($prefs);
2682      extract($thiscomment);
2683  
2684      extract(lAtts(array('link' => 1), $atts));
2685  
2686      $name = txpspecialchars($name);
2687  
2688      if ($link) {
2689          $web = comment_web();
2690          $nofollow = (@$comment_nofollow ? ' rel="nofollow"' : '');
2691  
2692          if (!empty($web)) {
2693              return href($name, $web, $nofollow);
2694          }
2695  
2696          if ($email && !$never_display_email) {
2697              return href($name, $encoder->entityObfuscateAddress('mailto:'.$email), $nofollow);
2698          }
2699      }
2700  
2701      return $name;
2702  }
2703  
2704  // -------------------------------------------------------------
2705  
2706  function comment_email()
2707  {
2708      global $thiscomment;
2709  
2710      assert_comment();
2711  
2712      return txpspecialchars($thiscomment['email']);
2713  }
2714  
2715  // -------------------------------------------------------------
2716  
2717  function comment_web()
2718  {
2719      global $thiscomment;
2720  
2721      assert_comment();
2722  
2723      if (preg_match('/^\S/', $thiscomment['web'])) {
2724          // Prepend default protocol 'http' for all non-local URLs.
2725          if (!preg_match('!^https?://|^#|^/[^/]!', $thiscomment['web'])) {
2726              $thiscomment['web'] = 'http://'.$thiscomment['web'];
2727          }
2728  
2729          return txpspecialchars($thiscomment['web']);
2730      }
2731  
2732      return '';
2733  }
2734  
2735  // -------------------------------------------------------------
2736  
2737  function comment_time($atts)
2738  {
2739      global $thiscomment, $comments_dateformat;
2740  
2741      assert_comment();
2742  
2743      extract(lAtts(array(
2744          'format' => $comments_dateformat,
2745          'gmt'    => '',
2746          'lang'   => '',
2747      ), $atts));
2748  
2749      return safe_strftime($format, $thiscomment['time'], $gmt, $lang);
2750  }
2751  
2752  // -------------------------------------------------------------
2753  
2754  function comment_message()
2755  {
2756      global $thiscomment;
2757  
2758      assert_comment();
2759  
2760      return $thiscomment['message'];
2761  }
2762  
2763  // -------------------------------------------------------------
2764  
2765  function comment_anchor()
2766  {
2767      global $thiscomment;
2768  
2769      assert_comment();
2770  
2771      $thiscomment['has_anchor_tag'] = 1;
2772  
2773      return '<a id="c'.$thiscomment['discussid'].'"></a>';
2774  }
2775  
2776  // -------------------------------------------------------------
2777  
2778  function author($atts)
2779  {
2780      global $thisarticle, $thisauthor, $s, $author;
2781  
2782      extract(lAtts(array(
2783          'escape'       => 'html',
2784          'link'         => 0,
2785          'title'        => 1,
2786          'section'      => '',
2787          'this_section' => 0,
2788          'format'       => '', // empty, link, or url
2789      ), $atts));
2790  
2791      // Synonym.
2792      if ($format === 'link') {
2793          $link = 1;
2794      }
2795  
2796      $fetchRealName = $link || $title || $format === 'url';
2797  
2798      if ($thisauthor) {
2799          $realname = $thisauthor['realname'];
2800          $name = $thisauthor['name'];
2801      } elseif ($author) {
2802          $name = $author;
2803      } else {
2804          assert_article();
2805          $name = $thisarticle['authorid'];
2806      }
2807  
2808      isset($realname) or $realname = $fetchRealName ? get_author_name($name) : $name;
2809  
2810      if ($title) {
2811          $display_name = $realname;
2812      } else {
2813          $display_name = $name;
2814      }
2815  
2816      if ($escape === 'html') {
2817          $display_name =  txpspecialchars($display_name);
2818      } elseif ($escape) {
2819          $display_name = txp_escape(array('escape' => $escape), $display_name);
2820      }
2821  
2822      if (!$link && $format !== 'url') {
2823          return $display_name;
2824      }
2825  
2826      if ($this_section && $s != 'default') {
2827          $section = $s;
2828      }
2829  
2830      $href = pagelinkurl(array(
2831              's'      => $section,
2832              'author' => $realname,
2833          ));
2834  
2835      return $format === 'url' ? $href : href($display_name, $href, ' rel="author"');
2836  }
2837  
2838  // -------------------------------------------------------------
2839  
2840  function author_email($atts)
2841  {
2842      global $thisarticle, $thisauthor;
2843  
2844      extract(lAtts(array(
2845          'escape' => 'html',
2846          'link'   => '',
2847      ), $atts));
2848  
2849      if ($thisauthor) {
2850          $email = get_author_email($thisauthor['name']);
2851      } else {
2852          assert_article();
2853          $email = get_author_email($thisarticle['authorid']);
2854      }
2855  
2856      if ($escape == 'html') {
2857          $display_email = txpspecialchars($email);
2858      } else {
2859          $display_email = $escape ? txp_escape(array('escape' => $escape), $email) : $email;
2860      }
2861  
2862      if ($link) {
2863          return email(array(
2864              'email'    => $email,
2865              'linktext' => $display_email,
2866          ));
2867      }
2868  
2869      return $display_email;
2870  }
2871  
2872  // -------------------------------------------------------------
2873  
2874  function if_author($atts, $thing = null)
2875  {
2876      global $author, $context, $thisauthor;
2877  
2878      extract(lAtts(array(
2879          'type' => 'article',
2880          'name' => '',
2881      ), $atts));
2882  
2883      $theType = ($type) ? $type == $context : true;
2884  
2885      if ($thisauthor) {
2886          $x = $name === '' || in_list($thisauthor['name'], $name);
2887      } elseif ($name) {
2888          $x = ($theType && in_list($author, $name));
2889      } else {
2890          $x = ($theType && (string) $author !== '');
2891      }
2892  
2893      return isset($thing) ? parse($thing, $x) : $x;
2894  }
2895  
2896  // -------------------------------------------------------------
2897  
2898  function if_article_author($atts, $thing = null)
2899  {
2900      global $thisarticle;
2901  
2902      assert_article();
2903  
2904      extract(lAtts(array('name' => ''), $atts));
2905  
2906      $author = $thisarticle['authorid'];
2907  
2908      $x = $name ? in_list($author, $name) : (string) $author !== '';
2909      return isset($thing) ? parse($thing, $x) : $x;
2910  }
2911  
2912  // -------------------------------------------------------------
2913  
2914  function if_logged_in($atts, $thing = null)
2915  {
2916      global $txp_groups;
2917      static $cache = array();
2918  
2919      extract(lAtts(array(
2920          'group' => '',
2921          'name'  => '',
2922      ), $atts));
2923  
2924      $user = isset($cache[$name]) ? $cache[$name] : ($cache[$name] = is_logged_in($name));
2925      $x = false;
2926  
2927      if ($user && $group !== '') {
2928          $privs = do_list($group);
2929          $groups = array_flip($txp_groups);
2930  
2931          foreach ($privs as &$priv) {
2932              if (!is_numeric($priv) && isset($groups[$priv])) {
2933                  $priv = $groups[$priv];
2934              } else {
2935                  $priv = intval($priv);
2936              }
2937          }
2938  
2939          $privs = array_unique($privs);
2940  
2941          if (in_array($user['privs'], $privs)) {
2942              $x = true;
2943          }
2944      } else {
2945          $x = (bool) $user;
2946      }
2947  
2948      return isset($thing) ? parse($thing, $x) : $x;
2949  }
2950  
2951  // -------------------------------------------------------------
2952  
2953  function txp_sandbox($atts = array(), $thing = null, $parse = true)
2954  {
2955      static $articles = array(), $uniqid = null, $stack = array(), $depth = null;
2956      global $thisarticle, $is_article_body;
2957  
2958      isset($depth) or $depth = get_pref('form_circular_depth', 15);
2959  
2960      extract($atts + array('field' => ''));
2961      unset($atts['field']);
2962  
2963      if (empty($id)) {
2964          assert_article();
2965          $id = $thisarticle['thisid'];
2966      } elseif (!isset($articles[$id])) {
2967          return;
2968      }
2969  
2970      if ($field) {
2971          if (!isset($stack[$id])) {
2972              $stack[$id] = 1;
2973          } elseif ($stack[$id] >= $depth) {
2974              trigger_error(gTxt('form_circular_reference', array(
2975                  '{name}' => '<txp:article id="'.$id.'"/>'
2976              )));
2977  
2978              return '';
2979          } else {
2980              $stack[$id]++;
2981          }
2982      }
2983  
2984      if ($parse) {
2985          $oldarticle = $thisarticle;
2986          isset($articles[$id]) and $thisarticle = $articles[$id];
2987          $was_article_body = $is_article_body;
2988          !$field or $is_article_body = 1;
2989  
2990          $thing = parse(isset($thing) ? $thing : $thisarticle[$field]);
2991  
2992          $is_article_body = $was_article_body;
2993          $thisarticle = $oldarticle;
2994      }
2995  
2996      !$field or $stack[$id]--;
2997  
2998      if (!preg_match('@<(?:'.TXP_PATTERN.'):@', $thing)) {
2999          return $thing;
3000      }
3001  
3002      if (!isset($uniqid)) {
3003          $uniqid = 'sandbox_'.strtr(uniqid('', true), '.', '_');
3004          Txp::get('\Textpattern\Tag\Registry')->register('txp_sandbox', $uniqid);
3005      }
3006  
3007      if ($field) {
3008          $tag = $field;
3009          unset($atts['id']);
3010      } else {
3011          $tag = $uniqid;
3012          $atts['id'] = $id;
3013      }
3014  
3015      isset($articles[$id]) or $articles[$id] = $thisarticle;
3016  
3017      return "<txp:$tag".($atts ? join_atts($atts) : '').">{$thing}</txp:$tag>";
3018  }
3019  
3020  // -------------------------------------------------------------
3021  
3022  function body($atts = array(), $thing = null)
3023  {
3024      return txp_sandbox(array('field' => 'body'), $thing);
3025  }
3026  
3027  // -------------------------------------------------------------
3028  
3029  function title($atts)
3030  {
3031      global $thisarticle, $prefs;
3032  
3033      assert_article();
3034  
3035      extract(lAtts(array(
3036          'escape'   => null,
3037          'no_widow' => '',
3038      ), $atts));
3039  
3040      $t = $escape === null ? escape_title($thisarticle['title']) : $thisarticle['title'];
3041  
3042      if ($no_widow && $escape === null) {
3043          $t = noWidow($t);
3044      }
3045  
3046      return $t;
3047  }
3048  
3049  // -------------------------------------------------------------
3050  
3051  function excerpt($atts = array(), $thing = null)
3052  {
3053      return txp_sandbox(array('field' => 'excerpt'), $thing);
3054  }
3055  
3056  // -------------------------------------------------------------
3057  
3058  function article_category($atts, $thing = null)
3059  {
3060      global $thisarticle, $s, $permlink_mode;
3061  
3062      assert_article();
3063  
3064      extract(lAtts(array(
3065          'number'       => 1,
3066          'class'        => '',
3067          'link'         => 0,
3068          'title'        => 0,
3069          'escape'       => true,
3070          'section'      => '',
3071          'this_section' => 0,
3072          'wraptag'      => '',
3073      ), $atts));
3074  
3075      $cat = 'category'.intval($number);
3076  
3077      if (!empty($thisarticle[$cat])) {
3078          $section = ($this_section) ? ($s == 'default' ? '' : $s) : $section;
3079          $category = $thisarticle[$cat];
3080  
3081          $label = $title ? fetch_category_title($category) : $category;
3082  
3083          if ($thing) {
3084              $out = href(
3085                  parse($thing),
3086                  pagelinkurl(array(
3087                      's' => $section,
3088                      'c' => $category,
3089                  )),
3090                  (($class && !$wraptag) ? ' class="'.txpspecialchars($class).'"' : '').
3091                  ($title ? ' title="'.txpspecialchars($label).'"' : '').
3092                  ($permlink_mode != 'messy' ? ' rel="tag"' : '')
3093              );
3094          } else {
3095              if ($escape) {
3096                  $label = txp_escape(array('escape' => $escape), $label);
3097              }
3098  
3099              if ($link) {
3100                  $out = href(
3101                      $label,
3102                      pagelinkurl(array(
3103                          's' => $section,
3104                          'c' => $category,
3105                      )),
3106                      ($permlink_mode != 'messy' ? ' rel="tag"' : '')
3107                  );
3108              } else {
3109                  $out = $label;
3110              }
3111          }
3112  
3113          return doTag($out, $wraptag, $class);
3114      }
3115  }
3116  
3117  // -------------------------------------------------------------
3118  
3119  function category($atts, $thing = null)
3120  {
3121      global $s, $c, $thiscategory, $context;
3122  
3123      extract(lAtts(array(
3124          'class'        => '',
3125          'link'         => 0,
3126          'name'         => '',
3127          'section'      => $s,
3128          'this_section' => 0,
3129          'title'        => 0,
3130          'type'         => 'article',
3131          'url'          => 0,
3132          'wraptag'      => '',
3133      ), $atts));
3134  
3135      if ($name) {
3136          $category = $name;
3137          $type = validContext($type);
3138      } elseif (!empty($thiscategory['name'])) {
3139          $category = $thiscategory['name'];
3140          $type = $thiscategory['type'];
3141      } else {
3142          $category = $c;
3143          $type = $context;
3144      }
3145  
3146      if ($category) {
3147          if ($this_section) {
3148              $section = ($s == 'default' ? '' : $s);
3149          } elseif (isset($thiscategory['section'])) {
3150              $section = $thiscategory['section'];
3151          }
3152  
3153          $label = txpspecialchars(($title) ? fetch_category_title($category, $type) : $category);
3154  
3155          $href = pagelinkurl(array(
3156              's'       => $section,
3157              'c'       => $category,
3158              'context' => $type,
3159          ));
3160  
3161          if ($thing) {
3162              $out = href(
3163                  parse($thing),
3164                  $href,
3165                  (($class && !$wraptag) ? ' class="'.txpspecialchars($class).'"' : '').
3166                  ($title ? ' title="'.$label.'"' : '')
3167              );
3168          } elseif ($link) {
3169              $out = href(
3170                  $label,
3171                  $href,
3172                  ($class && !$wraptag) ? ' class="'.txpspecialchars($class).'"' : ''
3173              );
3174          } elseif ($url) {
3175              $out = $href;
3176          } else {
3177              $out = $label;
3178          }
3179  
3180          return doTag($out, $wraptag, $class);
3181      }
3182  }
3183  
3184  // -------------------------------------------------------------
3185  
3186  function section($atts, $thing = null)
3187  {
3188      global $thisarticle, $s, $thissection;
3189  
3190      extract(lAtts(array(
3191          'class'   => '',
3192          'link'    => 0,
3193          'name'    => '',
3194          'title'   => 0,
3195          'url'     => 0,
3196          'wraptag' => '',
3197      ), $atts));
3198  
3199      if ($name) {
3200          $sec = $name;
3201      } elseif (!empty($thissection['name'])) {
3202          $sec = $thissection['name'];
3203      } elseif (!empty($thisarticle['section'])) {
3204          $sec = $thisarticle['section'];
3205      } else {
3206          $sec = $s;
3207      }
3208  
3209      if ($sec) {
3210          $label = txpspecialchars(($title) ? fetch_section_title($sec) : $sec);
3211  
3212          $href = pagelinkurl(array('s' => $sec));
3213  
3214          if ($thing) {
3215              $out = href(
3216                  parse($thing),
3217                  $href,
3218                  (($class && !$wraptag) ? ' class="'.txpspecialchars($class).'"' : '').
3219                  ($title ? ' title="'.$label.'"' : '')
3220              );
3221          } elseif ($link) {
3222              $out = href(
3223                  $label,
3224                  $href,
3225                  ($class && !$wraptag) ? ' class="'.txpspecialchars($class).'"' : ''
3226              );
3227          } elseif ($url) {
3228              $out = $href;
3229          } else {
3230              $out = $label;
3231          }
3232  
3233          return doTag($out, $wraptag, $class);
3234      }
3235  }
3236  
3237  // -------------------------------------------------------------
3238  
3239  function keywords($atts)
3240  {
3241      global $thisarticle;
3242  
3243      assert_article();
3244  
3245      extract(lAtts(array(
3246          'class'   => '',
3247          'break'   => ',',
3248          'wraptag' => '',
3249      ), $atts));
3250  
3251      $out = do_list_unique(txpspecialchars($thisarticle['keywords']));
3252  
3253      return doWrap($out, $wraptag, $break, $class);
3254  }
3255  
3256  // -------------------------------------------------------------
3257  
3258  function if_keywords($atts, $thing = null)
3259  {
3260      global $thisarticle;
3261  
3262      assert_article();
3263  
3264      extract(lAtts(array('keywords' => ''), $atts));
3265  
3266      $condition = empty($keywords)
3267          ? $thisarticle['keywords']
3268          : array_intersect(do_list($keywords), do_list($thisarticle['keywords']));
3269  
3270      $x = !empty($condition);
3271      return isset($thing) ? parse($thing, $x) : $x;
3272  }
3273  
3274  // -------------------------------------------------------------
3275  
3276  function if_article_image($atts, $thing = null)
3277  {
3278      global $thisarticle;
3279  
3280      assert_article();
3281  
3282      $x = !empty($thisarticle['article_image']);
3283      return isset($thing) ? parse($thing, $x) : $x;
3284  }
3285  
3286  // -------------------------------------------------------------
3287  
3288  function article_image($atts)
3289  {
3290      global $doctype, $thisarticle;
3291  
3292      assert_article();
3293  
3294      extract(lAtts(array(
3295          'escape'    => true,
3296          'title'     => '',
3297          'class'     => '',
3298          'html_id'   => '',
3299          'style'     => '',
3300          'width'     => '',
3301          'height'    => '',
3302          'thumbnail' => 0,
3303          'wraptag'   => '',
3304          'loading'   => null,
3305      ), $atts));
3306  
3307      if ($thisarticle['article_image']) {
3308          $image = $thisarticle['article_image'];
3309      } else {
3310          return '';
3311      }
3312  
3313      if (intval($image)) {
3314          $rs = safe_row("*", 'txp_image', "id = ".intval($image));
3315  
3316          if (empty($rs)) {
3317              trigger_error(gTxt('unknown_image'));
3318  
3319              return '';
3320          }
3321  
3322          if ($thumbnail && empty($rs['thumbnail'])) {
3323              return '';
3324          }
3325  
3326          $width = ($width == '') ? (($thumbnail) ? $rs['thumb_w'] : $rs['w']) : $width;
3327          $height = ($height == '') ? (($thumbnail) ? $rs['thumb_h'] : $rs['h']) : $height;
3328  
3329          extract($rs);
3330  
3331          if ($title === true) {
3332              $title = $caption;
3333          }
3334  
3335          $out = '<img src="'.imagesrcurl($id, $ext, !empty($atts['thumbnail'])).
3336              '" alt="'.txpspecialchars($alt, ENT_QUOTES, 'UTF-8', false).'"'.
3337              ($title ? ' title="'.txpspecialchars($title, ENT_QUOTES, 'UTF-8', false).'"' : '');
3338      } else {
3339          $out = '<img src="'.txpspecialchars($image).'" alt=""'.
3340              ($title && $title !== true ? ' title="'.txpspecialchars($title).'"' : '');
3341      }
3342  
3343      if ($loading && $doctype == 'html5' && in_array($loading, array('auto', 'eager', 'lazy'))) {
3344          $out .= ' loading="'.$loading.'"';
3345      }
3346  
3347      $out .=
3348          (($html_id && !$wraptag) ? ' id="'.txpspecialchars($html_id).'"' : '').
3349          (($class && !$wraptag) ? ' class="'.txpspecialchars($class).'"' : '').
3350          ($style ? ' style="'.txpspecialchars($style).'"' : '').
3351          ($width ? ' width="'.(int) $width.'"' : '').
3352          ($height ? ' height="'.(int) $height.'"' : '').
3353          ' />';
3354  
3355      return ($wraptag) ? doTag($out, $wraptag, $class, '', $html_id) : $out;
3356  }
3357  
3358  // -------------------------------------------------------------
3359  
3360  function search_result_title($atts)
3361  {
3362      return permlink($atts, '<txp:title />');
3363  }
3364  
3365  // -------------------------------------------------------------
3366  
3367  function search_result_excerpt($atts)
3368  {
3369      global $thisarticle, $pretext;
3370  
3371      assert_article();
3372  
3373      extract(lAtts(array(
3374          'break'     => ' &#8230;', // Deprecated in 4.7.0.
3375          'hilight'   => 'strong',
3376          'limit'     => 5,
3377          'separator' => ' &#8230;',
3378      ), $atts));
3379  
3380      if (isset($atts['break'])) {
3381          trigger_error(gTxt('deprecated_attribute_with', array(
3382              '{name}' => 'break',
3383              '{with}' => 'separator',
3384          )), E_USER_NOTICE);
3385  
3386          if (!isset($atts['separator'])) {
3387              $separator = $break;
3388          }
3389      }
3390  
3391      $m = $pretext['m'];
3392      $q = $pretext['q'];
3393  
3394      $quoted = ($q[0] === '"') && ($q[strlen($q) - 1] === '"');
3395      $q = $quoted ? trim(trim($q, '"')) : trim($q);
3396  
3397      $result = preg_replace('/\s+/', ' ', strip_tags(str_replace('><', '> <', $thisarticle['body'])));
3398  
3399      if ($quoted || empty($m) || $m === 'exact') {
3400          $regex_search = '/(?:\G|\s).{0,50}'.preg_quote($q, '/').'.{0,50}(?:\s|$)/iu';
3401          $regex_hilite = '/('.preg_quote($q, '/').')/i';
3402      } else {
3403          $regex_search = '/(?:\G|\s).{0,50}('.preg_replace('/\s+/', '|', preg_quote($q, '/')).').{0,50}(?:\s|$)/iu';
3404          $regex_hilite = '/('.preg_replace('/\s+/', '|', preg_quote($q, '/')).')/i';
3405      }
3406  
3407      preg_match_all($regex_search, $result, $concat);
3408      $concat = $concat[0];
3409  
3410      for ($i = 0, $r = array(); $i < min($limit, count($concat)); $i++) {
3411          $r[] = trim($concat[$i]);
3412      }
3413  
3414      $concat = join($separator.n, $r);
3415      $concat = preg_replace('/^[^>]+>/U', '', $concat);
3416      $concat = preg_replace($regex_hilite, "<$hilight>$1</$hilight>", $concat);
3417  
3418      return ($concat) ? trim($separator.$concat.$separator) : '';
3419  }
3420  
3421  // -------------------------------------------------------------
3422  
3423  function search_result_url($atts)
3424  {
3425      global $thisarticle;
3426  
3427      assert_article();
3428  
3429      $l = permlinkurl($thisarticle);
3430  
3431      return permlink($atts, $l);
3432  }
3433  
3434  // -------------------------------------------------------------
3435  
3436  function search_result_date($atts)
3437  {
3438      assert_article();
3439  
3440      return posted($atts);
3441  }
3442  
3443  // -------------------------------------------------------------
3444  
3445  function search_result_count($atts)
3446  {
3447      global $thispage;
3448  
3449      if (empty($thispage)) {
3450          return postpone_process();
3451      }
3452  
3453      extract(lAtts(array(
3454          'text'   => null,
3455          'pageby' => 1,
3456      ), $atts));
3457  
3458      $by = (int)$pageby or $by = 1;
3459      $t = ceil(@$thispage[$pageby === true ? 'numPages' : 'grand_total']/$by);
3460  
3461      if (!isset($text)) {
3462          $text = $pageby === true || $by > 1 ? gTxt($t == 1 ? 'page' : 'pages') : gTxt($t == 1 ? 'article_found' : 'articles_found');
3463      }
3464  
3465      return $t.($text ? ' '.$text : '');
3466  }
3467  
3468  // -------------------------------------------------------------
3469  
3470  function image_index($atts)
3471  {
3472      trigger_error(gTxt('deprecated_tag'), E_USER_NOTICE);
3473  
3474      global $c;
3475  
3476      lAtts(array(
3477          'break'    => br,
3478          'wraptag'  => '',
3479          'class'    => __FUNCTION__,
3480          'category' => $c,
3481          'limit'    => 0,
3482          'offset'   => 0,
3483          'sort'     => 'name ASC',
3484      ), $atts);
3485  
3486      if (!isset($atts['category'])) {
3487          $atts['category'] = $c;
3488      }
3489  
3490      if (!isset($atts['class'])) {
3491          $atts['class'] = __FUNCTION__;
3492      }
3493  
3494      if ($atts['category']) {
3495          return images($atts);
3496      }
3497  
3498      return '';
3499  }
3500  
3501  // -------------------------------------------------------------
3502  
3503  function image_display($atts)
3504  {
3505      trigger_error(gTxt('deprecated_tag'), E_USER_NOTICE);
3506  
3507      global $p;
3508  
3509      if ($p) {
3510          return image(array('id' => $p));
3511      }
3512  }
3513  
3514  // -------------------------------------------------------------
3515  
3516  function images($atts, $thing = null)
3517  {
3518      global $s, $c, $context, $thisimage, $thisarticle, $thispage, $pretext;
3519  
3520      extract(lAtts(array(
3521          'name'        => '',
3522          'id'          => '',
3523          'category'    => '',
3524          'author'      => '',
3525          'realname'    => '',
3526          'extension'   => '',
3527          'thumbnail'   => '',
3528          'size'        => '',
3529          'auto_detect' => 'article, category, author',
3530          'break'       => br,
3531          'wraptag'     => '',
3532          'class'       => __FUNCTION__,
3533          'html_id'     => '',
3534          'form'        => '',
3535          'pageby'      => '',
3536          'limit'       => 0,
3537          'offset'      => 0,
3538          'sort'        => 'name ASC',
3539      ), $atts));
3540  
3541      $safe_sort = sanitizeForSort($sort);
3542      $where = array();
3543      $has_content = $thing || $form;
3544      $filters = isset($atts['id']) || isset($atts['name']) || isset($atts['category']) || isset($atts['author']) || isset($atts['realname']) || isset($atts['extension']) || isset($atts['size']) || $thumbnail === '1' || $thumbnail === '0';
3545      $context_list = (empty($auto_detect) || $filters) ? array() : do_list_unique($auto_detect);
3546      $pageby = ($pageby == 'limit') ? $limit : $pageby;
3547  
3548      if ($name) {
3549          $where[] = "name IN ('".join("','", doSlash(do_list_unique($name)))."')";
3550      }
3551  
3552      if ($category) {
3553          $where[] = "category IN ('".join("','", doSlash(do_list_unique($category)))."')";
3554      }
3555  
3556      if ($id) {
3557          $id = join(',', array_map('intval', do_list_unique($id, array(',', '-'))));
3558          $where[] = "id IN ($id)";
3559      }
3560  
3561      if ($author) {
3562          $where[] = "author IN ('".join("','", doSlash(do_list_unique($author)))."')";
3563      }
3564  
3565      if ($realname) {
3566          $authorlist = safe_column("name", 'txp_users', "RealName IN ('".join("','", doArray(doSlash(do_list_unique($realname)), 'urldecode'))."')");
3567          if ($authorlist) {
3568              $where[] = "author IN ('".join("','", doSlash($authorlist))."')";
3569          }
3570      }
3571  
3572      if ($extension) {
3573          $where[] = "ext IN ('".join("','", doSlash(do_list_unique($extension)))."')";
3574      }
3575  
3576      if ($thumbnail === '0' || $thumbnail === '1') {
3577          $where[] = "thumbnail = $thumbnail";
3578      }
3579  
3580      // Handle aspect ratio filtering.
3581      if ($size === 'portrait') {
3582          $where[] = "h > w";
3583      } elseif ($size === 'landscape') {
3584          $where[] = "w > h";
3585      } elseif ($size === 'square') {
3586          $where[] = "w = h";
3587      } elseif (is_numeric($size)) {
3588          $where[] = "ROUND(w/h, 2) = $size";
3589      } elseif (strpos($size, ':') !== false) {
3590          $ratio = explode(':', $size);
3591          $ratiow = $ratio[0];
3592          $ratioh = !empty($ratio[1]) ? $ratio[1] : '';
3593  
3594          if (is_numeric($ratiow) && is_numeric($ratioh)) {
3595              $where[] = "ROUND(w/h, 2) = ".round($ratiow/$ratioh, 2);
3596          } elseif (is_numeric($ratiow)) {
3597              $where[] = "w = $ratiow";
3598          } elseif (is_numeric($ratioh)) {
3599              $where[] = "h = $ratioh";
3600          }
3601      }
3602  
3603      // If no images are selected, try...
3604      if (!$where && !$filters) {
3605          foreach ($context_list as $ctxt) {
3606              switch ($ctxt) {
3607                  case 'article':
3608                      // ...the article image field.
3609                      if ($thisarticle && !empty($thisarticle['article_image'])) {
3610                          if (!is_numeric(str_replace(array(',', '-', ' '), '', $thisarticle['article_image']))) {
3611                              return article_image(compact('class', 'html_id', 'wraptag'));
3612                          }
3613  
3614                          $id = join(",", array_map('intval', do_list_unique($thisarticle['article_image'], array(',', '-'))));
3615  
3616                          // Note: This clause will squash duplicate ids.
3617                          $where[] = "id IN ($id)";
3618                      }
3619                      break;
3620                  case 'category':
3621                      // ...the global category in the URL.
3622                      if ($context == 'image' && !empty($c)) {
3623                          $where[] = "category = '".doSlash($c)."'";
3624                      }
3625                      break;
3626                  case 'author':
3627                      // ...the global author in the URL.
3628                      if ($context == 'image' && !empty($pretext['author'])) {
3629                          $where[] = "author = '".doSlash($pretext['author'])."'";
3630                      }
3631                      break;
3632              }
3633              // Only one context can be processed.
3634              if ($where) {
3635                  break;
3636              }
3637          }
3638      }
3639  
3640      // Order of ids in 'id' attribute overrides default 'sort' attribute.
3641      if (empty($atts['sort']) && $id) {
3642          $safe_sort = "FIELD(id, $id)";
3643      }
3644  
3645      // If nothing matches from the filterable attributes, output nothing.
3646      if (!$where && $filters) {
3647          return '';
3648      }
3649  
3650      // If no images are filtered, start with all images.
3651      if (!$where) {
3652          $where[] = "1 = 1";
3653      }
3654  
3655      $where = join(" AND ", $where);
3656  
3657      // Set up paging if required.
3658      if ($limit && $pageby) {
3659          $pg = (!$pretext['pg']) ? 1 : $pretext['pg'];
3660          $pgoffset = $offset + (($pg - 1) * $pageby);
3661  
3662          if (empty($thispage)) {
3663              $grand_total = safe_count('txp_image', $where);
3664              $total = $grand_total - $offset;
3665              $numPages = ($pageby > 0) ? ceil($total / $pageby) : 1;
3666  
3667              // Send paging info to txp:newer and txp:older.
3668              $pageout['pg']          = $pg;
3669              $pageout['numPages']    = $numPages;
3670              $pageout['s']           = $s;
3671              $pageout['c']           = $c;
3672              $pageout['context']     = 'image';
3673              $pageout['grand_total'] = $grand_total;
3674              $pageout['total']       = $total;
3675              $thispage = $pageout;
3676          }
3677      } else {
3678          $pgoffset = $offset;
3679      }
3680  
3681      $qparts = array(
3682          $where,
3683          "ORDER BY ".$safe_sort,
3684          ($limit) ? "LIMIT ".intval($pgoffset).", ".intval($limit) : '',
3685      );
3686  
3687      $rs = safe_rows_start("*", 'txp_image', join(' ', $qparts));
3688  
3689      if ($rs) {
3690          $out = array();
3691          $count = 0;
3692          $last = numRows($rs);
3693  
3694          if (isset($thisimage)) {
3695              $old_image = $thisimage;
3696          }
3697  
3698          while ($a = nextRow($rs)) {
3699              ++$count;
3700              $thisimage = image_format_info($a);
3701              $thisimage['is_first'] = ($count == 1);
3702              $thisimage['is_last'] = ($count == $last);
3703  
3704              if (!$has_content) {
3705                  $url = pagelinkurl(array(
3706                      'c'       => $thisimage['category'],
3707                      'context' => 'image',
3708                      's'       => $s,
3709                      'p'       => $thisimage['id'],
3710                  ));
3711                  $src = image_url(array('thumbnail' => '1'));
3712                  $thing = href(
3713                      '<img src="'.$src.'" alt="'.txpspecialchars($thisimage['alt']).'" />',
3714                      $url
3715                  );
3716              }
3717  
3718              $out[] = ($thing) ? parse($thing) : parse_form($form);
3719          }
3720  
3721          $thisimage = (isset($old_image) ? $old_image : null);
3722  
3723          if ($out) {
3724              return doWrap($out, $wraptag, compact('break', 'class', 'html_id'));
3725          }
3726      }
3727  
3728      return '';
3729  }
3730  
3731  // -------------------------------------------------------------
3732  
3733  function image_info($atts)
3734  {
3735      extract(lAtts(array(
3736          'name'       => '',
3737          'id'         => '',
3738          'type'       => 'caption',
3739          'escape'     => true,
3740          'wraptag'    => '',
3741          'class'      => '',
3742          'break'      => '',
3743      ), $atts));
3744  
3745      $validItems = array('id', 'name', 'category', 'category_title', 'alt', 'caption', 'ext', 'author', 'w', 'h', 'thumb_w', 'thumb_h', 'date');
3746      $type = do_list($type);
3747  
3748      $out = array();
3749      empty($escape) or $escape = compact('escape');
3750  
3751      if ($imageData = imageFetchInfo($id, $name)) {
3752          $imageData['category_title'] = fetch_category_title($imageData['category'], 'image');
3753  
3754          foreach ($type as $item) {
3755              if (in_array($item, $validItems)) {
3756                  if (isset($imageData[$item])) {
3757                      $out[] = $escape ?
3758                          txp_escape($escape, $imageData[$item]) : $imageData[$item];
3759                  }
3760              } else {
3761                  trigger_error(gTxt('invalid_attribute_value', array('{name}' => $item)), E_USER_NOTICE);
3762              }
3763          }
3764      }
3765  
3766      return doWrap($out, $wraptag, $break, $class);
3767  }
3768  
3769  // -------------------------------------------------------------
3770  
3771  function image_url($atts, $thing = null)
3772  {
3773      extract(lAtts(array(
3774          'name'      => '',
3775          'id'        => '',
3776          'thumbnail' => 0,
3777          'link'      => 'auto',
3778      ), $atts));
3779  
3780      if (($name || $id) && $thing) {
3781          global $thisimage;
3782          $stash = $thisimage;
3783      }
3784  
3785      if ($thisimage = imageFetchInfo($id, $name)) {
3786          $url = imagesrcurl($thisimage['id'], $thisimage['ext'], $thumbnail);
3787          $link = ($link == 'auto') ? (($thing) ? 1 : 0) : $link;
3788          $out = ($thing) ? parse($thing) : $url;
3789          $out = ($link) ? href($out, $url) : $out;
3790      }
3791  
3792      if (isset($stash)) {
3793          $thisimage = $stash;
3794      }
3795  
3796      return isset($out) ? $out : '';
3797  }
3798  
3799  // -------------------------------------------------------------
3800  
3801  function image_author($atts)
3802  {
3803      global $s;
3804  
3805      extract(lAtts(array(
3806          'name'         => '',
3807          'id'           => '',
3808          'link'         => 0,
3809          'title'        => 1,
3810          'section'      => '',
3811          'this_section' => '',
3812      ), $atts));
3813  
3814      if ($imageData = imageFetchInfo($id, $name)) {
3815          $author_name = get_author_name($imageData['author']);
3816          $display_name = txpspecialchars(($title) ? $author_name : $imageData['author']);
3817  
3818          $section = ($this_section) ? ($s == 'default' ? '' : $s) : $section;
3819  
3820          $author = ($link)
3821              ? href($display_name, pagelinkurl(array(
3822                  's'       => $section,
3823                  'author'  => $author_name,
3824                  'context' => 'image',
3825              )))
3826              : $display_name;
3827  
3828          return $author;
3829      }
3830  }
3831  
3832  // -------------------------------------------------------------
3833  
3834  function image_date($atts)
3835  {
3836      extract(lAtts(array(
3837          'name'   => '',
3838          'id'     => '',
3839          'format' => '',
3840      ), $atts));
3841  
3842      if ($imageData = imageFetchInfo($id, $name)) {
3843          // Not a typo: use fileDownloadFormatTime() since it's fit for purpose.
3844          $out = fileDownloadFormatTime(array(
3845              'ftime'  => $imageData['date'],
3846              'format' => $format,
3847          ));
3848  
3849          return $out;
3850      }
3851  }
3852  
3853  // -------------------------------------------------------------
3854  
3855  function if_thumbnail($atts, $thing = null)
3856  {
3857      global $thisimage;
3858  
3859      assert_image();
3860  
3861      $x = ($thisimage['thumbnail'] == 1);
3862      return isset($thing) ? parse($thing, $x) : $x;
3863  }
3864  
3865  // -------------------------------------------------------------
3866  
3867  function if_comments($atts, $thing = null)
3868  {
3869      global $thisarticle;
3870  
3871      assert_article();
3872  
3873      $x = ($thisarticle['comments_count'] > 0);
3874      return isset($thing) ? parse($thing, $x) : $x;
3875  }
3876  
3877  // -------------------------------------------------------------
3878  
3879  function if_comments_allowed($atts, $thing = null)
3880  {
3881      global $thisarticle;
3882  
3883      assert_article();
3884  
3885      $x = checkCommentsAllowed($thisarticle['thisid']);
3886      return isset($thing) ? parse($thing, $x) : $x;
3887  }
3888  
3889  // -------------------------------------------------------------
3890  
3891  function if_comments_disallowed($atts, $thing = null)
3892  {
3893      global $thisarticle;
3894  
3895      assert_article();
3896  
3897      $x = !checkCommentsAllowed($thisarticle['thisid']);
3898      return isset($thing) ? parse($thing, $x) : $x;
3899  }
3900  
3901  // -------------------------------------------------------------
3902  
3903  function if_individual_article($atts, $thing = null)
3904  {
3905      global $is_article_list;
3906  
3907      $x = ($is_article_list == false);
3908      return isset($thing) ? parse($thing, $x) : $x;
3909  }
3910  
3911  // -------------------------------------------------------------
3912  
3913  function if_article_list($atts, $thing = null)
3914  {
3915      global $is_article_list, $pretext;
3916      static $defaults = array('s', 'c', 'q', 'month', 'author');
3917  
3918      $x = ($is_article_list == true);
3919  
3920      if ($x && !empty($atts)) {
3921          extract(lAtts(array('type' => ''), $atts));
3922  
3923          foreach ($type === true ? $defaults : do_list_unique($type) as $q) {
3924              switch ($q) {
3925                  case 's':
3926                      $x = !empty($pretext['s']) && $pretext['s'] != 'default';
3927                      break;
3928                  default:
3929                      $x = !empty($pretext[$q]) || !isset($pretext[$q]) && gps($q);
3930              }
3931  
3932              if ($x) {
3933                  break;
3934              }
3935          }
3936      }
3937  
3938      return isset($thing) ? parse($thing, $x) : $x;
3939  }
3940  
3941  /**
3942   * Returns article keywords.
3943   *
3944   * @param  array  $atts Tag attributes
3945   * @return string
3946   */
3947  
3948  function meta_keywords($atts)
3949  {
3950      global $id_keywords;
3951  
3952      extract(lAtts(array(
3953          'escape'    => null,
3954          'format'    => 'meta', // or empty for raw value
3955          'separator' => null,
3956      ), $atts));
3957  
3958      $out = '';
3959  
3960      if ($id_keywords) {
3961          $content = ($escape === null) ? txpspecialchars($id_keywords) : $id_keywords;
3962  
3963          if ($separator !== null) {
3964              $content = implode($separator, do_list($content));
3965          }
3966  
3967          if ($format === 'meta') {
3968              // Can't use tag_void() since it escapes its content.
3969              $out = '<meta name="keywords" content="'.$content.'" />';
3970          } else {
3971              $out = $content;
3972          }
3973      }
3974  
3975      return $out;
3976  }
3977  
3978  /**
3979   * Returns article, section or category meta description info.
3980   *
3981   * @param  array  $atts Tag attributes
3982   * @return string
3983   */
3984  
3985  function meta_description($atts)
3986  {
3987      extract(lAtts(array(
3988          'escape' => null,
3989          'format' => 'meta', // or empty for raw value
3990          'type'   => null,
3991      ), $atts));
3992  
3993      $out = '';
3994      $content = getMetaDescription($type);
3995  
3996      if ($content) {
3997          $content = ($escape === null ? txpspecialchars($content) : $content);
3998  
3999          if ($format === 'meta') {
4000              $out = '<meta name="description" content="'.$content.'" />';
4001          } else {
4002              $out = $content;
4003          }
4004      }
4005  
4006      return $out;
4007  }
4008  
4009  /**
4010   * Determines if there is meta description content in the given context.
4011   *
4012   * @param  array  $atts  Tag attributes
4013   * @param  string $thing Tag container content
4014   * @return string
4015   */
4016  
4017  function if_description($atts, $thing = null)
4018  {
4019      extract(lAtts(array('type' => null), $atts));
4020  
4021      $content = getMetaDescription($type);
4022      $x = !empty($content);
4023  
4024      return isset($thing) ? parse($thing, $x) : $x;
4025  }
4026  
4027  
4028  // -------------------------------------------------------------
4029  
4030  function meta_author($atts)
4031  {
4032      global $id_author;
4033  
4034      extract(lAtts(array(
4035          'escape' => null,
4036          'format' => 'meta', // or empty for raw value
4037          'title'  => 0,
4038      ), $atts));
4039  
4040      $out = '';
4041  
4042      if ($id_author) {
4043          $display_name = ($title) ? get_author_name($id_author) : $id_author;
4044          $display_name = ($escape === null) ? txpspecialchars($display_name) : $display_name;
4045  
4046          if ($format === 'meta') {
4047              // Can't use tag_void() since it escapes its content.
4048              $out = '<meta name="author" content="'.$display_name.'" />';
4049          } else {
4050              $out = $display_name;
4051          }
4052      }
4053  
4054      return $out;
4055  }
4056  
4057  // -------------------------------------------------------------
4058  
4059  function permlink($atts, $thing = null)
4060  {
4061      global $pretext, $thisarticle, $txp_context;
4062      static $lAtts = array(
4063          'class'   => '',
4064          'id'      => '',
4065          'style'   => '',
4066          'title'   => '',
4067          'context' => null,
4068      );
4069  
4070      $old_context = $txp_context;
4071  
4072      if (!isset($atts['context'])) {
4073          if (empty($txp_context)) {
4074              $atts = lAtts($lAtts, $atts);
4075          } else {
4076              $atts = lAtts($lAtts + $txp_context, $atts);
4077              $txp_context = array_intersect_key($atts, $txp_context);
4078          }
4079      } elseif ($atts['context'] === true) {
4080          $atts = lAtts($lAtts, $atts);
4081      } else {
4082          $extralAtts = array_fill_keys(do_list_unique($atts['context']), null);
4083          $atts = lAtts($lAtts + $extralAtts, $atts);
4084          $extralAtts = array_intersect_key($atts, $extralAtts);
4085      }
4086  
4087      $id = $atts['id'];
4088  
4089      if (!$id && !assert_article()) {
4090          return;
4091      }
4092  
4093      $txp_context = get_context(isset($extralAtts) ? $extralAtts : $atts['context']);
4094      $url = $id ? permlinkurl_id($id) : permlinkurl($thisarticle);
4095      $txp_context = $old_context;
4096  
4097      if ($url) {
4098          if ($thing === null) {
4099              return $url;
4100          }
4101  
4102          return tag(parse($thing), 'a', array(
4103              'rel'   => 'bookmark',
4104              'href'  => $url,
4105              'title' => $atts['title'],
4106              'style' => $atts['style'],
4107              'class' => $atts['class'],
4108          ));
4109      }
4110  }
4111  
4112  // -------------------------------------------------------------
4113  
4114  function lang()
4115  {
4116      return txpspecialchars(LANG);
4117  }
4118  
4119  // -------------------------------------------------------------
4120  
4121  function breadcrumb($atts, $thing = null)
4122  {
4123      global $c, $s, $sitename, $thiscategory, $context;
4124  
4125      extract(lAtts(array(
4126          'type'      => $context,
4127          'category'  => $c,
4128          'section'   => $s,
4129          'wraptag'   => 'p',
4130          'separator' => '&#160;&#187;&#160;',
4131          'limit'     => null,
4132          'offset'    => 0,
4133          'link'      => 1,
4134          'label'     => $sitename,
4135          'title'     => '',
4136          'class'     => '',
4137          'linkclass' => '',
4138      ), $atts));
4139  
4140      $content = array();
4141      $label = txpspecialchars($label);
4142      $type = $type === true ? $context : validContext($type);
4143      $section != 'default' or $section = '';
4144  
4145      if ($link && $label) {
4146          $label = doTag($label, 'a', $linkclass, ' href="'.hu.'"');
4147      }
4148  
4149      if (!empty($section)) {
4150          $section_title = ($title) ? fetch_section_title($section) : $section;
4151          $section_title_html = escape_title($section_title);
4152          $content[] = ($link)
4153              ? (doTag($section_title_html, 'a', $linkclass, ' href="'.pagelinkurl(array('s' => $s)).'"'))
4154              : $section_title_html;
4155      }
4156  
4157      if (!$category) {
4158          $catpath = array();
4159      } else {
4160          $catpath = array_reverse(getRootPath($category, $type));
4161      }
4162  
4163      if ($limit || $offset) {
4164          $offset = (int)$offset < 0 ? (int)$offset - 1 : (int)$offset;
4165          $catpath = array_slice($catpath, $offset, isset($limit) ? (int)$limit : null);
4166      }
4167  
4168      $oldcategory = isset($thiscategory) ? $thiscategory : null;
4169  
4170      foreach ($catpath as $thiscategory) {
4171          $category_title_html = isset($thing) ? parse($thing) : ($title ? escape_title($thiscategory['title']) : $thiscategory['name']);
4172          $content[] = ($link)
4173              ? doTag($category_title_html, 'a', $linkclass, ' href="'.pagelinkurl(array(
4174                  'c'       => $thiscategory['name'],
4175                  'context' => $type,
4176                  's'       => $section
4177              )).'"')
4178              : $category_title_html;
4179      }
4180  
4181      $thiscategory = isset($oldcategory) ? $oldcategory : null;
4182  
4183      // Add the label at the end, to prevent breadcrumb for homepage.
4184      if ($content) {
4185          return doWrap($label ? array_merge(array($label), $content) : $content, $wraptag, $separator, $class);
4186      }
4187  }
4188  
4189  //------------------------------------------------------------------------
4190  
4191  function if_excerpt($atts, $thing = null)
4192  {
4193      global $thisarticle;
4194  
4195      assert_article();
4196  
4197      $x = trim($thisarticle['excerpt']) !== '';
4198      return isset($thing) ? parse($thing, $x) : $x;
4199  }
4200  
4201  // -------------------------------------------------------------
4202  // Searches use default page. This tag allows you to use different templates if searching
4203  // -------------------------------------------------------------
4204  
4205  function if_search($atts, $thing = null)
4206  {
4207      global $pretext;
4208  
4209      $x = !empty($pretext['q']);
4210      return isset($thing) ? parse($thing, $x) : $x;
4211  }
4212  
4213  // -------------------------------------------------------------
4214  
4215  function if_search_results($atts, $thing = null)
4216  {
4217      global $pretext, $thispage, $is_article_list;
4218  
4219      if (empty($pretext['q']) || empty($thispage)) {
4220          return $is_article_list ? postpone_process() : '';
4221      }
4222  
4223      extract(lAtts(array(
4224          'min' => 1,
4225          'max' => 0,
4226      ), $atts));
4227  
4228      $results = (int) $thispage['grand_total'];
4229  
4230      $x = $results >= $min && (!$max || $results <= $max);
4231      return isset($thing) ? parse($thing, $x) : $x;
4232  }
4233  
4234  // -------------------------------------------------------------
4235  
4236  function if_category($atts, $thing = null)
4237  {
4238      global $c, $context, $thiscategory;
4239  
4240      extract(lAtts(array(
4241          'category' => false,
4242          'type'     => false,
4243          'name'     => false,
4244          'parent'   => 0,
4245      ), $atts));
4246  
4247      if ($category === false) {
4248          $category = $c;
4249          $theType = $context;
4250      } elseif ($category === true) {
4251          $category = empty($thiscategory['name']) ? $c : $thiscategory['name'];
4252          $theType = empty($thiscategory['type']) ? $context : $thiscategory['type'];
4253      } else {
4254          $theType = $type && $type !== true ? validContext($type) : $context;
4255          ($parent || $type === false) or $parent = true;
4256          $category = trim($category);
4257      }
4258  
4259      if ($type && $type !== true && $theType !== $type) {
4260          $x = false;
4261      } else {
4262          $parentname = $parent && is_numeric((string)$parent);
4263          $x = $name === false ? !empty($category) : $parentname || in_list($category, $name);
4264      }
4265  
4266      if ($x && $parent && $category) {
4267          $path = array_column(getRootPath($category, $theType), 'name');
4268  
4269          if (!$parentname) {
4270              $name = $parent;
4271              $parent = true;
4272          }
4273  
4274          $names = do_list_unique($name);
4275  
4276          if ($parent === true) {
4277              $x = $path && ($name === false || array_intersect($path, $names));
4278          } else {
4279              ($parent = (int)$parent) >= 0 or $parent = count($path) + $parent - 1;
4280              $x = isset($path[$parent]) && ($name === false || in_array($path[$parent], $names));
4281          }
4282      }
4283  
4284      return isset($thing) ? parse($thing, $x) : $x;
4285  }
4286  
4287  // -------------------------------------------------------------
4288  
4289  function if_article_category($atts, $thing = null)
4290  {
4291      global $thisarticle;
4292  
4293      assert_article();
4294  
4295      extract(lAtts(array(
4296          'name'   => '',
4297          'number' => '',
4298      ), $atts));
4299  
4300      $cats = array();
4301  
4302      if ($number) {
4303          if (!empty($thisarticle['category'.$number])) {
4304              $cats = array($thisarticle['category'.$number]);
4305          }
4306      } else {
4307          if (!empty($thisarticle['category1'])) {
4308              $cats[] = $thisarticle['category1'];
4309          }
4310  
4311          if (!empty($thisarticle['category2'])) {
4312              $cats[] = $thisarticle['category2'];
4313          }
4314  
4315          $cats = array_unique($cats);
4316      }
4317  
4318      if ($name) {
4319          $cats = array_intersect(do_list($name), $cats);
4320      }
4321  
4322      $x = !empty($cats);
4323  
4324      return isset($thing) ? parse($thing, $x) : $x;
4325  }
4326  
4327  // -------------------------------------------------------------
4328  
4329  function if_section($atts, $thing = null)
4330  {
4331      global $s, $thissection;
4332  
4333      extract(lAtts(array('name' => false, 'section' => false), $atts));
4334  
4335      switch ($section) {
4336          case true: $section = isset($thissection) ? $thissection['name'] : $s; break;
4337          case false: $section = $s; break;
4338      }
4339  
4340      $section !== 'default' or $section = '';
4341      $name === false or $name = do_list($name);
4342  
4343      if ($section) {
4344          $x = $name === false || in_array($section, $name);
4345      } else {
4346          $x = $name !== false && (in_array('', $name) || in_array('default', $name));
4347      }
4348  
4349      return isset($thing) ? parse($thing, $x) : $x;
4350  }
4351  
4352  // -------------------------------------------------------------
4353  
4354  function if_article_section($atts, $thing = null)
4355  {
4356      global $thisarticle, $txp_sections;
4357  
4358      assert_article();
4359  
4360      extract(lAtts(array('name' => ''), $atts));
4361  
4362      $section = $thisarticle['section'];
4363  
4364      $x = $name === true ? !empty($txp_sections[$section]['page']) : in_list($section, $name);
4365      return isset($thing) ? parse($thing, $x) : $x;
4366  }
4367  
4368  // -------------------------------------------------------------
4369  
4370  function php($atts = null, $thing = null)
4371  {
4372      global $is_article_body, $thisarticle, $prefs, $pretext;
4373  
4374      $error = null;
4375  
4376      if (empty($is_article_body)) {
4377          if (empty($prefs['allow_page_php_scripting'])) {
4378              $error = 'php_code_disabled_page';
4379          }
4380      } else {
4381          if (!empty($prefs['allow_article_php_scripting'])) {
4382              if (!has_privs('article.php', $thisarticle['authorid'])) {
4383                  $error = 'php_code_forbidden_user';
4384              }
4385          } else {
4386              $error = 'php_code_disabled_article';
4387          }
4388      }
4389  
4390      if ($thing !== null) {
4391          ob_start();
4392  
4393          if ($error) {
4394              trigger_error(gTxt($error));
4395          } else {
4396              eval($thing);
4397          }
4398  
4399          return ob_get_clean();
4400      }
4401  
4402      return empty($error);
4403  }
4404  
4405  // -------------------------------------------------------------
4406  
4407  function txp_header($atts)
4408  {
4409      extract(lAtts(array(
4410          'name'    => 'Content-Type',
4411          'replace' => 1,
4412          'value'   => isset($atts['name']) ? true : 'text/html; charset=utf-8',
4413          'break'   => ''
4414      ), $atts));
4415  
4416      $out = set_headers(array($name => $value), $replace);
4417  
4418      return $out ? doWrap($out, null, $break) : null;
4419  }
4420  
4421  // -------------------------------------------------------------
4422  
4423  function custom_field($atts, $thing = null)
4424  {
4425      global $thisarticle;
4426  
4427      assert_article();
4428  
4429      extract(lAtts(array(
4430          'name'    => get_pref('custom_1_set'),
4431          'escape'  => null,
4432          'default' => '',
4433      ), $atts));
4434  
4435      $name = strtolower($name);
4436  
4437      if (!isset($thisarticle[$name])) {
4438          trigger_error(gTxt('field_not_found', array('{name}' => $name)), E_USER_NOTICE);
4439  
4440          return '';
4441      }
4442  
4443      if (!isset($thing)) {
4444          $thing = $thisarticle[$name] !== '' ? $thisarticle[$name] : $default;
4445      }
4446  
4447      $thing = ($escape === null ? txpspecialchars($thing) : parse($thing));
4448  
4449      return txp_sandbox(array('field' => 'custom_field') + $atts, $thing, false);
4450  }
4451  
4452  // -------------------------------------------------------------
4453  
4454  function if_custom_field($atts, $thing = null)
4455  {
4456      global $thisarticle;
4457  
4458      assert_article();
4459  
4460      extract($atts = lAtts(array(
4461          'name'      => get_pref('custom_1_set'),
4462          'value'     => null,
4463          'match'     => 'exact',
4464          'separator' => '',
4465      ), $atts));
4466  
4467      $name = strtolower($name);
4468  
4469      if (!isset($thisarticle[$name])) {
4470          trigger_error(gTxt('field_not_found', array('{name}' => $name)), E_USER_NOTICE);
4471  
4472          return '';
4473      }
4474  
4475      if ($value !== null) {
4476          $cond = txp_match($atts, $thisarticle[$name]);
4477      } else {
4478          $cond = ($thisarticle[$name] !== '');
4479      }
4480  
4481      return isset($thing) ? parse($thing, !empty($cond)) : !empty($cond);
4482  }
4483  
4484  // -------------------------------------------------------------
4485  
4486  function site_url($atts)
4487  {
4488      extract(lAtts(array(
4489          'type' => '',
4490      ), $atts));
4491  
4492      return $type === 'admin' ? ahu : hu;
4493  }
4494  
4495  // -------------------------------------------------------------
4496  
4497  function error_message()
4498  {
4499      global $txp_error_message;
4500  
4501      return $txp_error_message;
4502  }
4503  
4504  // -------------------------------------------------------------
4505  
4506  function error_status()
4507  {
4508      global $txp_error_status;
4509  
4510      return $txp_error_status;
4511  }
4512  
4513  // -------------------------------------------------------------
4514  
4515  function if_status($atts, $thing = null)
4516  {
4517      global $pretext, $txp_error_code;
4518  
4519      extract(lAtts(array('status' => '200'), $atts));
4520  
4521      $page_status = $txp_error_code
4522          ? $txp_error_code
4523          : $pretext['status'];
4524  
4525      $x = $status == $page_status;
4526      return isset($thing) ? parse($thing, $x) : $x;
4527  }
4528  
4529  // -------------------------------------------------------------
4530  
4531  function page_url($atts, $thing = null)
4532  {
4533      global $pretext, $txp_context;
4534      static $specials = null, $internals = array('id', 's', 'c', 'context', 'q', 'm', 'p', 'month', 'author', 'f'),
4535          $lAtts = array(
4536              'type'    => null,
4537              'default' => false,
4538              'escape'  => null,
4539              'context' => null
4540          );
4541  
4542      isset($specials) or $specials = array(
4543          'admin_root'  => ahu,
4544          'images_root' => ihu.get_pref('img_dir'),
4545          'themes_root' => hu.get_pref('skin_dir'),
4546          'theme_path'  => hu.get_pref('skin_dir').'/'.$pretext['skin'],
4547          'theme'       => $pretext['skin'],
4548      );
4549  
4550      $old_context = $txp_context;
4551  
4552      if (!isset($atts['context'])) {
4553          if (empty($txp_context)) {
4554              $atts = lAtts($lAtts, $atts);
4555          } else {
4556              $atts = lAtts($lAtts + $txp_context, $atts);
4557              $txp_context = array_intersect_key($atts, $txp_context);
4558          }
4559      } elseif ($atts['context'] === true) {
4560          $atts = lAtts($lAtts, $atts);
4561      } else {
4562          $extralAtts = array_fill_keys(do_list_unique($atts['context']), null);
4563          $atts = lAtts($lAtts + $extralAtts, $atts);
4564          $extralAtts = array_intersect_key($atts, $extralAtts);
4565      }
4566  
4567      extract($atts, EXTR_SKIP);
4568  
4569      $txp_context = get_context(isset($extralAtts) ? $extralAtts : $context, $internals);
4570  
4571      if ($default !== false) {
4572          if ($default === true) {
4573              if (isset($type)) {
4574                  unset($txp_context[$type]);
4575              } else {
4576                  $txp_context = array();
4577              }
4578          } elseif (in_array($type, $internals)) {
4579              $txp_context[$type] = $default;
4580          }
4581      }
4582  
4583      if (!isset($type)) {
4584          $type = 'request_uri';
4585      }
4586  
4587      if (isset($thing)) {
4588          $out = parse($thing);
4589      } elseif (isset($context)) {
4590          $out = pagelinkurl($txp_context);
4591          $escape === null or $out = str_replace('&amp;', '&', $out);
4592      } elseif (isset($specials[$type])) {
4593          $out = $specials[$type];
4594      } elseif ($type == 'pg' && $pretext['pg'] == '') {
4595          $out = '1';
4596      } elseif (isset($pretext[$type]) && is_bool($default)) {
4597          $out = $escape === null ? txpspecialchars($pretext[$type]) : $pretext[$type];
4598      } else {
4599          $out = gps($type, $default);
4600          !is_array($out) or $out = implode(',', $out);
4601          $escape !== null or $out = txpspecialchars($out);
4602      }
4603  
4604      $txp_context = $old_context;
4605  
4606      return $out;
4607  }
4608  
4609  // -------------------------------------------------------------
4610  
4611  function if_different($atts, $thing = null)
4612  {
4613      static $last, $tested;
4614  
4615      extract(lAtts(array(
4616          'test'    => null,
4617          'not'     => ''
4618      ), $atts));
4619  
4620      $key = md5($thing);
4621      $out = isset($test) ? $test : parse($thing);
4622  
4623      if (isset($test)) {
4624          if ($different = !isset($tested[$key]) || $out != $tested[$key]) {
4625              $tested[$key] = $out;
4626          }
4627      } else {
4628          if ($different = !isset($last[$key]) || $out != $last[$key]) {
4629              $last[$key] = $out;
4630          }
4631      }
4632  
4633      $condition = $not ? !$different : $different;
4634  
4635      return isset($test) ?
4636          parse($thing, $condition) :
4637          ($condition ? $out : parse($thing, false));
4638  }
4639  
4640  // -------------------------------------------------------------
4641  
4642  function if_first($atts, $thing = null, $type = 'article')
4643  {
4644      global ${"this$type"};
4645  
4646      $assert = 'assert_'.$type;
4647      $assert();
4648  
4649      $x = !empty(${"this$type"}['is_first']);
4650      return isset($thing) ? parse($thing, $x) : $x;
4651  }
4652  
4653  // -------------------------------------------------------------
4654  
4655  function if_last($atts, $thing = null, $type = 'article')
4656  {
4657      global ${"this$type"};
4658  
4659      $assert = 'assert_'.$type;
4660      $assert();
4661  
4662      $x = !empty(${"this$type"}['is_last']);
4663      return isset($thing) ? parse($thing, $x) : $x;
4664  }
4665  
4666  // -------------------------------------------------------------
4667  
4668  function if_plugin($atts, $thing = null)
4669  {
4670      global $plugins, $plugins_ver;
4671  
4672      extract(lAtts(array(
4673          'name'    => '',
4674          'version' => '',
4675      ), $atts));
4676  
4677      $x = @in_array($name, $plugins) && (!$version || version_compare($plugins_ver[$name], $version) >= 0);
4678      return isset($thing) ? parse($thing, $x) : $x;
4679  }
4680  
4681  // -------------------------------------------------------------
4682  
4683  function file_download_list($atts, $thing = null)
4684  {
4685      global $s, $c, $context, $thisfile, $thispage, $pretext;
4686  
4687      extract(lAtts(array(
4688          'break'       => br,
4689          'category'    => '',
4690          'author'      => '',
4691          'realname'    => '',
4692          'auto_detect' => 'category, author',
4693          'class'       => __FUNCTION__,
4694          'form'        => 'files',
4695          'id'          => '',
4696          'pageby'      => '',
4697          'limit'       => 10,
4698          'offset'      => 0,
4699          'month'       => '',
4700          'time'        => null,
4701          'sort'        => 'filename asc',
4702          'wraptag'     => '',
4703          'status'      => STATUS_LIVE,
4704      ), $atts));
4705  
4706      if (!is_numeric($status)) {
4707          $status = getStatusNum($status);
4708      }
4709  
4710      // Note: status treated slightly differently.
4711      $where = array();
4712      $filters = isset($atts['id']) || isset($atts['category']) || isset($atts['author']) || isset($atts['realname']) || isset($atts['status']);
4713      $context_list = (empty($auto_detect) || $filters) ? array() : do_list_unique($auto_detect);
4714      $pageby = ($pageby == 'limit') ? $limit : $pageby;
4715  
4716      if ($category) {
4717          $where[] = "category IN ('".join("','", doSlash(do_list_unique($category)))."')";
4718      }
4719  
4720      $ids = $id ? array_map('intval', do_list_unique($id, array(',', '-'))) : array();
4721  
4722      if ($ids) {
4723          $where[] = "id IN ('".join("','", $ids)."')";
4724      }
4725  
4726      if ($author) {
4727          $where[] = "author IN ('".join("','", doSlash(do_list_unique($author)))."')";
4728      }
4729  
4730      if ($realname) {
4731          $authorlist = safe_column("name", 'txp_users', "RealName IN ('".join("','", doArray(doSlash(do_list_unique($realname)), 'urldecode'))."')");
4732          if ($authorlist) {
4733              $where[] = "author IN ('".join("','", doSlash($authorlist))."')";
4734          }
4735      }
4736  
4737      // If no files are selected, try...
4738      if (!$where && !$filters) {
4739          foreach ($context_list as $ctxt) {
4740              switch ($ctxt) {
4741                  case 'category':
4742                      // ...the global category in the URL.
4743                      if ($context == 'file' && !empty($c)) {
4744                          $where[] = "category = '".doSlash($c)."'";
4745                      }
4746                      break;
4747                  case 'author':
4748                      // ...the global author in the URL.
4749                      if ($context == 'file' && !empty($pretext['author'])) {
4750                          $where[] = "author = '".doSlash($pretext['author'])."'";
4751                      }
4752                      break;
4753              }
4754  
4755              // Only one context can be processed.
4756              if ($where) {
4757                  break;
4758              }
4759          }
4760      }
4761  
4762      if ($status) {
4763          $where[] = "status = '".doSlash($status)."'";
4764      } elseif (!$where && $filters) {
4765          // If nothing matches, output nothing.
4766          return '';
4767      }
4768  
4769      if ($time === null || $time || $month) {
4770          $where[] = buildTimeSql($month, $time === null ? 'past' : $time, 'created');
4771      }
4772  
4773      $where = join(" AND ", $where);
4774  
4775      // Set up paging if required.
4776      if ($limit && $pageby) {
4777          $pg = (!$pretext['pg']) ? 1 : $pretext['pg'];
4778          $pgoffset = $offset + (($pg - 1) * $pageby);
4779  
4780          if (empty($thispage)) {
4781              $grand_total = safe_count('txp_file', $where);
4782              $total = $grand_total - $offset;
4783              $numPages = ($pageby > 0) ? ceil($total/$pageby) : 1;
4784  
4785              // Send paging info to txp:newer and txp:older.
4786              $pageout['pg']          = $pg;
4787              $pageout['numPages']    = $numPages;
4788              $pageout['s']           = $s;
4789              $pageout['c']           = $c;
4790              $pageout['context']     = 'file';
4791              $pageout['grand_total'] = $grand_total;
4792              $pageout['total']       = $total;
4793              $thispage = $pageout;
4794          }
4795      } else {
4796          $pgoffset = $offset;
4797      }
4798  
4799      // Preserve order of custom file ids unless 'sort' attribute is set.
4800      if (!empty($ids) && empty($atts['sort'])) {
4801          $safe_sort = "FIELD(id, ".join(',', $ids).")";
4802      } else {
4803          $safe_sort = sanitizeForSort($sort);
4804      }
4805  
4806      $qparts = array(
4807          "ORDER BY ".$safe_sort,
4808          ($limit) ? "LIMIT ".intval($pgoffset).", ".intval($limit) : '',
4809      );
4810  
4811      $rs = safe_rows_start("*", 'txp_file', $where.' '.join(' ', $qparts));
4812  
4813      if ($rs) {
4814          $count = 0;
4815          $last = numRows($rs);
4816          $out = array();
4817  
4818          while ($a = nextRow($rs)) {
4819              ++$count;
4820              $thisfile = file_download_format_info($a);
4821              $thisfile['is_first'] = ($count == 1);
4822              $thisfile['is_last'] = ($count == $last);
4823  
4824              $out[] = ($thing) ? parse($thing) : parse_form($form);
4825  
4826              $thisfile = '';
4827          }
4828  
4829          if ($out) {
4830              return doWrap($out, $wraptag, $break, $class);
4831          }
4832      }
4833  
4834      return '';
4835  }
4836  
4837  // -------------------------------------------------------------
4838  
4839  function file_download($atts, $thing = null)
4840  {
4841      global $thisfile;
4842  
4843      extract(lAtts(array(
4844          'filename' => '',
4845          'form'     => 'files',
4846          'id'       => '',
4847      ), $atts));
4848  
4849      $from_form = false;
4850  
4851      if ($id) {
4852          $thisfile = fileDownloadFetchInfo('id = '.intval($id).' and created <= '.now('created'));
4853      } elseif ($filename) {
4854          $thisfile = fileDownloadFetchInfo("filename = '".doSlash($filename)."' and created <= ".now('created'));
4855      } else {
4856          assert_file();
4857  
4858          $from_form = true;
4859      }
4860  
4861      if ($thisfile) {
4862          $out = ($thing) ? parse($thing) : parse_form($form);
4863  
4864          // Cleanup: this wasn't called from a form, so we don't want this
4865          // value remaining.
4866          if (!$from_form) {
4867              $thisfile = '';
4868          }
4869  
4870          return $out;
4871      }
4872  }
4873  
4874  // -------------------------------------------------------------
4875  
4876  function file_download_link($atts, $thing = null)
4877  {
4878      global $thisfile;
4879  
4880      extract(lAtts(array(
4881          'filename' => '',
4882          'id'       => '',
4883      ), $atts));
4884  
4885      $from_form = false;
4886  
4887      if ($id) {
4888          $thisfile = fileDownloadFetchInfo('id = '.intval($id).' and created <= '.now('created'));
4889      } elseif ($filename) {
4890          $thisfile = fileDownloadFetchInfo("filename = '".doSlash($filename)."' and created <= ".now('created'));
4891      } else {
4892          assert_file();
4893  
4894          $from_form = true;
4895      }
4896  
4897      if ($thisfile) {
4898          $url = filedownloadurl($thisfile['id'], $thisfile['filename']);
4899  
4900          $out = ($thing) ? href(parse($thing), $url) : $url;
4901  
4902          // Cleanup: this wasn't called from a form, so we don't want this
4903          // value remaining
4904          if (!$from_form) {
4905              $thisfile = '';
4906          }
4907  
4908          return $out;
4909      }
4910  }
4911  
4912  // -------------------------------------------------------------
4913  
4914  function file_download_size($atts)
4915  {
4916      global $thisfile;
4917  
4918      assert_file();
4919  
4920      extract(lAtts(array(
4921          'decimals' => 2,
4922          'format'   => '',
4923      ), $atts));
4924  
4925      if (is_numeric($decimals) && $decimals >= 0) {
4926          $decimals = intval($decimals);
4927      } else {
4928          $decimals = 2;
4929      }
4930  
4931      if (isset($thisfile['size'])) {
4932          $format_unit = strtolower(substr($format, 0, 1));
4933  
4934          return format_filesize($thisfile['size'], $decimals, $format_unit);
4935      } else {
4936          return '';
4937      }
4938  }
4939  
4940  // -------------------------------------------------------------
4941  
4942  function file_download_time($atts, $thing = null, $time = 'created')
4943  {
4944      global $thisfile;
4945  
4946      assert_file();
4947  
4948      extract(lAtts(array('format' => ''), $atts));
4949  
4950      if (!empty($thisfile[$time])) {
4951          return fileDownloadFormatTime(array(
4952              'ftime'  => $thisfile[$time],
4953              'format' => $format,
4954          ));
4955      }
4956  }
4957  
4958  // -------------------------------------------------------------
4959  
4960  function file_download_id()
4961  {
4962      global $thisfile;
4963  
4964      assert_file();
4965  
4966      return $thisfile['id'];
4967  }
4968  
4969  // -------------------------------------------------------------
4970  
4971  function file_download_name($atts)
4972  {
4973      global $thisfile;
4974  
4975      assert_file();
4976  
4977      extract(lAtts(array('title' => 0), $atts));
4978  
4979      return ($title) ? $thisfile['title'] : $thisfile['filename'];
4980  }
4981  
4982  // -------------------------------------------------------------
4983  
4984  function file_download_category($atts)
4985  {
4986      global $thisfile;
4987  
4988      assert_file();
4989  
4990      extract(lAtts(array('title' => 0), $atts));
4991  
4992      if ($thisfile['category']) {
4993          $category = ($title)
4994              ? fetch_category_title($thisfile['category'], 'file')
4995              : $thisfile['category'];
4996  
4997          return $category;
4998      }
4999  }
5000  
5001  // -------------------------------------------------------------
5002  
5003  function file_download_author($atts)
5004  {
5005      global $thisfile, $s;
5006  
5007      assert_file();
5008  
5009      extract(lAtts(array(
5010          'link'         => 0,
5011          'title'        => 1,
5012          'section'      => '',
5013          'this_section' => '',
5014      ), $atts));
5015  
5016      if ($thisfile['author']) {
5017          $author_name = get_author_name($thisfile['author']);
5018          $display_name = txpspecialchars(($title) ? $author_name : $thisfile['author']);
5019  
5020          $section = ($this_section) ? ($s == 'default' ? '' : $s) : $section;
5021  
5022          $author = ($link)
5023              ? href($display_name, pagelinkurl(array(
5024                  's'       => $section,
5025                  'author'  => $author_name,
5026                  'context' => 'file',
5027              )))
5028              : $display_name;
5029  
5030          return $author;
5031      }
5032  }
5033  
5034  // -------------------------------------------------------------
5035  
5036  function file_download_downloads()
5037  {
5038      global $thisfile;
5039  
5040      assert_file();
5041  
5042      return $thisfile['downloads'];
5043  }
5044  
5045  // -------------------------------------------------------------
5046  
5047  function file_download_description($atts)
5048  {
5049      global $thisfile;
5050  
5051      assert_file();
5052  
5053      extract(lAtts(array('escape' => null), $atts));
5054  
5055      if ($thisfile['description']) {
5056          return ($escape === null)
5057              ? txpspecialchars($thisfile['description'])
5058              : $thisfile['description'];
5059      }
5060  }
5061  
5062  // -------------------------------------------------------------
5063  
5064  function hide($atts = array(), $thing = null)
5065  {
5066      if (!isset($atts['process'])) {
5067          return '';
5068      }
5069  
5070      global $pretext;
5071  
5072      extract(lAtts(array('process' => null), $atts));
5073  
5074      if (!$process) {
5075          return $pretext['secondpass'] < get_pref('secondpass', 1) ? postpone_process() : $thing;
5076      } elseif (is_numeric($process)) {
5077          return abs($process) > $pretext['secondpass'] + 1 ?
5078              postpone_process($process) :
5079              ($process > 0 ? parse($thing) : '<txp:hide>'.parse($thing).'</txp:hide>');
5080      } elseif ($process) {
5081          parse($thing);
5082      }
5083  
5084      return '';
5085  }
5086  
5087  // -------------------------------------------------------------
5088  
5089  function rsd()
5090  {
5091      global $prefs;
5092  
5093      trigger_error(gTxt('deprecated_tag'), E_USER_NOTICE);
5094  
5095      return ($prefs['enable_xmlrpc_server']) ? '<link rel="EditURI" type="application/rsd+xml" title="RSD" href="'.hu.'rpc/" />' : '';
5096  }
5097  
5098  // -------------------------------------------------------------
5099  
5100  function variable($atts, $thing = null)
5101  {
5102      global $variable, $trace;
5103  
5104      $set = isset($thing) || isset($atts['value']) || isset($atts['add']) || isset($atts['reset']) ? '' : null;
5105  
5106      extract(lAtts(array(
5107          'escape'    => $set,
5108          'name'      => '',
5109          'value'     => null,
5110          'add'       => null,
5111          'reset'     => null,
5112          'separator' => null,
5113          'output'    => null
5114      ), $atts));
5115  
5116      $var = isset($variable[$name]) ? $variable[$name] : null;
5117  
5118      if (empty($name)) {
5119          trigger_error(gTxt('variable_name_empty'));
5120      } elseif ($set === null && !isset($var)) {
5121          $trace->log("[<txp:variable>: Unknown variable '$name']");
5122      } else {
5123          if ($add === true) {
5124              empty($thing) or $thing = parse($thing);
5125  
5126              if (!isset($value)) {
5127                  $add = isset($thing) ? $thing : 1;
5128              } elseif ($value === true) {
5129                  $add = $var;
5130                  !isset($thing) or $var = $thing;
5131              } else {
5132                  $add = isset($thing) ? $thing : $var;
5133                  $var = $value;
5134              }
5135          } elseif ($value === true) {
5136              isset($var) or $var = (isset($thing) ? parse($thing) : null);
5137          } else {
5138              $var = isset($value) ?
5139                  $value :
5140                  (isset($thing) ? parse($thing) : $var);
5141          }
5142  
5143          if (isset($add)) {
5144              if (!isset($separator) && is_numeric($add) && (empty($var) || is_numeric($var))) {
5145                  $var += $add;
5146              } else {
5147                  $var .= ($var ? $separator : '').$add;
5148              }
5149          }
5150      }
5151  
5152      if ($set !== null) {
5153          $var = $escape ? txp_escape(array('escape' => $escape), $var) : $var;
5154  
5155          if (isset($reset)) {
5156              $variable[$name] = $reset === true ? null : $reset;
5157              isset($output) or $output = 1;
5158          } else {
5159              $variable[$name] = $var;
5160          }
5161      } else {
5162          isset($output) or $output = 1;
5163      }
5164  
5165      return !$output ? '' : ((int)$output ? $var : txp_escape(array('escape' => $output), $var));
5166  }
5167  
5168  // -------------------------------------------------------------
5169  
5170  function if_variable($atts, $thing = null)
5171  {
5172      global $variable;
5173  
5174      extract($atts = lAtts(array(
5175          'name'      => '',
5176          'value'     => false,
5177          'match'     => 'exact',
5178          'separator' => '',
5179      ), $atts));
5180  
5181      if (empty($name)) {
5182          trigger_error(gTxt('variable_name_empty'));
5183  
5184          return '';
5185      }
5186  
5187      if (isset($variable[$name])) {
5188          $x = $value === false ? true : txp_match($atts, $variable[$name]);
5189      } else {
5190          $x = false;
5191      }
5192  
5193      return isset($thing) ? parse($thing, $x) : $x;
5194  }
5195  
5196  // -------------------------------------------------------------
5197  
5198  function if_request($atts, $thing = null)
5199  {
5200      extract($atts = lAtts(array(
5201          'name'      => '',
5202          'type'      => 'request',
5203          'value'     => null,
5204          'match'     => 'exact',
5205          'separator' => '',
5206      ), $atts));
5207  
5208      switch ($type = strtoupper($type)) {
5209          case 'REQUEST':
5210          case 'GET':
5211          case 'POST':
5212          case 'COOKIE':
5213          case 'SERVER':
5214              global ${'_'.$type};
5215              $what = isset(${'_'.$type}[$name]) ? ${'_'.$type}[$name] : null;
5216              $x = txp_match($atts, $what);
5217              break;
5218          case 'NAME':
5219              $x = txp_match($atts, $name);
5220              break;
5221          default:
5222              $x = false;
5223              trigger_error(gTxt('invalid_attribute_value', array('{name}' => 'type')), E_USER_NOTICE);
5224      }
5225  
5226      return isset($thing) ? parse($thing, $x) : $x;
5227  }
5228  
5229  // -------------------------------------------------------------
5230  
5231  function txp_eval($atts, $thing = null)
5232  {
5233      global $prefs, $txp_tag, $txp_atts;
5234      static $xpath = null, $functions = null;
5235  
5236      unset($txp_atts['evaluate']);
5237      $staged = null;
5238  
5239      extract(lAtts(array(
5240          'query' => null,
5241          'test'  => !isset($atts['query']),
5242      ), $atts));
5243  
5244      if (!isset($thing) && isset($atts['test']) && $atts['test'] !== true) {
5245          $thing = $atts['test'];
5246          $test = null;
5247      }
5248  
5249      if (!isset($query) || $query === true) {
5250          $x = true;
5251      } elseif (!($query = trim($query))) {
5252          $x = $query;
5253      } elseif (class_exists('DOMDocument')) {
5254          if (!isset($xpath)) {
5255              $xpath = new DOMXpath(new DOMDocument);
5256              $functions = do_list_unique(get_pref('txp_evaluate_functions'));
5257              $_functions = array();
5258  
5259              foreach ($functions as $function) {
5260                  list($key, $val) = do_list($function, '=') + array(null, $function);
5261  
5262                  if (function_exists($val)) {
5263                      $_functions[$key] = $val;
5264                  }
5265              }
5266  
5267              if ($_functions) {
5268                  $functions = implode('|', array_keys($_functions));
5269                  $xpath->registerNamespace('php', 'http://php.net/xpath');
5270                  $xpath->registerPHPFunctions($_functions);
5271              } else {
5272                  $functions = false;
5273              }
5274  
5275              $prefs['_txp_evaluate_functions'] = $_functions;
5276          }
5277  
5278          if ($functions) {
5279              $query = preg_replace_callback('/\b('.$functions.')\s*\(\s*(\)?)/',
5280                  function ($match) {
5281                      global $prefs;
5282                      $function = empty($prefs['_txp_evaluate_functions'][$match[1]]) ? $match[1] : $prefs['_txp_evaluate_functions'][$match[1]];
5283  
5284                      return "php:function('$function'".($match[2] ? ')' : ',');
5285                  },
5286                  $query
5287              );
5288          }
5289  
5290          if (strpos($query, '<+>') !== false) {
5291              $staged = $x = true;
5292          } else {
5293              $x = $xpath->evaluate($query);
5294  
5295              if ($x instanceof DOMNodeList) {
5296                  $x = $x->length;
5297              }
5298          }
5299      } else {
5300          trigger_error(gTxt('missing_dom_extension'));
5301          return '';
5302      }
5303  
5304      if (!isset($thing)) {
5305          return $test === true ? !empty($x) : $x;
5306      } elseif (empty($x)) {
5307          return parse($thing, false);
5308      }
5309  
5310      $txp_atts['evaluate'] = $test;
5311      $x = parse($thing);
5312      unset($txp_atts['evaluate']);
5313  
5314      if ($txp_tag) {
5315          if ($staged) {
5316              $quoted = txp_escape(array('escape' => 'quote'), $x);
5317              $query = str_replace('<+>', $quoted, $query);
5318              $query = $xpath->evaluate($query);
5319              $query = $query instanceof DOMNodeList ? $query->length : $query;
5320  
5321              if (empty($query)) {
5322                  return parse($thing, false);
5323              } else {
5324                  return $test === true ? $x : $query;
5325              }
5326          }
5327      } else {
5328          $txp_atts = null;
5329          $x = parse($thing, false);
5330      }
5331  
5332      return $test === null && $query !== true ? !empty($x) : $x;
5333  }
5334  
5335  // -------------------------------------------------------------
5336  
5337  function txp_escape($atts, $thing = '')
5338  {
5339      global $prefs;
5340      static $textile = null, $decimal = null, $spellout = null, $ordinal = null,
5341          $mb = null, $LocaleInfo = null, $tr = array("'" => "',\"'\",'");
5342  
5343      if (empty($atts['escape'])) {
5344          return $thing;
5345      }
5346  
5347      extract(lAtts(array('escape' => true), $atts, false));
5348  
5349      $escape = $escape === true ? array('html') : do_list(strtolower($escape));
5350      $filter = $tidy = false;
5351  
5352      isset($mb) or $mb = extension_loaded('mbstring') ? 'mb_' : '';
5353  
5354      foreach ($escape as $attr) {
5355          switch ($attr) {
5356              case 'html':
5357                  $thing = txpspecialchars($thing);
5358                  break;
5359              case 'url':
5360                  $thing = $tidy ? rawurlencode($thing) : urlencode($thing);
5361                  break;
5362              case 'js':
5363                  $thing = escape_js($thing);
5364                  break;
5365              case 'json':
5366                  $thing = substr(json_encode($thing, JSON_UNESCAPED_UNICODE), 1, -1);
5367                  break;
5368              case 'integer':
5369                  !$filter or $thing = do_list($thing);
5370                  // no break
5371              case 'number': case 'float': case 'spell': case 'ordinal':
5372                  isset($LocaleInfo) or $LocaleInfo = localeconv();
5373                  $dec_point = $LocaleInfo['decimal_point'];
5374                  $thousands_sep = utf8_encode($LocaleInfo['thousands_sep']);
5375                  !$thousands_sep or $thing = str_replace($thousands_sep, '', $thing);
5376                  $dec_point == '.' or $thing = str_replace($dec_point, '.', $thing);
5377  
5378                  if (is_array($thing)) {// integer mode
5379                      $value = $tidy ?
5380                          array_map(function ($str) {
5381                              return filter_var($str, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
5382                          }, $thing) :
5383                          $thing;
5384                  } else {
5385                      $value = floatval($tidy ?
5386                          filter_var($thing, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION) :
5387                          $thing
5388                      );
5389                  }
5390  
5391                  switch ($attr) {
5392                      case 'integer':
5393                          $thing = $filter ? implode(',', array_filter(array_map('intval', $value))) : intval($value);
5394                          break;
5395                      case 'number':
5396                          isset($decimal)
5397                              or !($decimal = class_exists('NumberFormatter'))
5398                              or $decimal = new NumberFormatter(LANG, NumberFormatter::DECIMAL);
5399  
5400                          if ($decimal) {
5401                              $thing = $decimal->format($value);
5402                          } else {
5403                              $thing = number_format($value, 3, $dec_point, $thousands_sep);
5404                              $thing = rtrim(rtrim($thing, '0'), $dec_point);
5405                          }
5406                          break;
5407                      case 'spell':
5408                          isset($spellout)
5409                              or !($spellout = class_exists('NumberFormatter'))
5410                              or $spellout = new NumberFormatter(LANG, NumberFormatter::SPELLOUT);
5411  
5412                          if ($spellout && ($tidy || is_numeric($thing))) {
5413                              $thing = $spellout->format($value);
5414                          }
5415                          break;
5416                      case 'ordinal':
5417                          isset($ordinal)
5418                              or !($ordinal = class_exists('NumberFormatter'))
5419                              or $ordinal = new NumberFormatter(LANG, NumberFormatter::ORDINAL);
5420  
5421                          if ($ordinal && ($tidy || is_numeric($thing))) {
5422                              $thing = $ordinal->format($value);
5423                          }
5424                          break;
5425                      default:
5426                          $thing = $dec_point != '.' ? str_replace($dec_point, '.', $value) : $value;
5427                  }
5428                  break;
5429              case 'tags':
5430                  $thing = strip_tags($thing);
5431                  break;
5432              case 'upper': case 'lower':
5433                  $function = ($mb && mb_detect_encoding($thing) != 'ASCII' ? 'mb_strto' : 'strto').$attr;
5434                  $thing = $function($thing);
5435                  break;
5436              case 'title':
5437                  $thing = $mb && mb_detect_encoding($thing) != 'ASCII' ?
5438                      mb_convert_case($thing, MB_CASE_TITLE) : ucwords($thing);
5439                  break;
5440              case 'trim': case 'ltrim': case 'rtrim':
5441                  $filter = true;
5442                  $thing = is_int($thing) ? ($thing ? $thing : '') : $attr($thing);
5443                  break;
5444              case 'tidy':
5445                  $thing = preg_replace('/\s+/', ' ', trim($thing));
5446                  $tidy = true;
5447                  break;
5448              case 'textile':
5449                  if ($textile === null) {
5450                      $textile = Txp::get('\Textpattern\Textile\Parser');
5451                  }
5452  
5453                  $thing = $textile->parse($tidy ? ' '.$thing : $thing);
5454                  !$tidy or $thing = ltrim($thing);
5455                  break;
5456              case 'quote':
5457                  $thing = strpos($thing, "'") === false ? "'$thing'" : "concat('".strtr($thing, $tr)."')";
5458                  break;
5459              default:
5460                  $thing = preg_replace('@</?'.($tidy ? preg_quote($attr) : $attr).'\b[^<>]*>@Usi', '', $thing);
5461          }
5462      }
5463  
5464      return $thing;
5465  }
5466  
5467  // -------------------------------------------------------------
5468  
5469  function txp_wraptag($atts, $thing = '')
5470  {
5471      extract(lAtts(array(
5472          'label'    => '',
5473          'labeltag' => '',
5474          'wraptag'  => '',
5475          'class'    => '',
5476          'html_id'  => '',
5477          'trim'     => null,
5478          'replace'  => null,
5479          'default'  => null,
5480      ), $atts, false));
5481  
5482      !isset($default) or trim($thing) !== '' or $thing = $default;
5483  
5484      if ($replace === true) {
5485          $sep = isset($trim) && $trim !== true ? $trim : ',';
5486          $thing = isset($trim) ? do_list_unique($thing, $sep, $trim === true ? TEXTPATTERN_STRIP_EMPTY : TEXTPATTERN_STRIP_EMPTY_STRING) : array_unique(explode($sep, $thing));
5487          $thing = implode($sep, $thing);
5488      } elseif (isset($trim)) {
5489          if ($trim === true) {
5490              $thing = isset($replace) ? preg_replace('/\s+/', $replace, trim($thing)) : trim($thing);
5491          } elseif (strlen($trim) > 2 && preg_match('/([^\\\w\s]).+\1[UsiAmuS]*$/As', $trim)) {
5492              $thing = preg_replace($trim, $replace, $thing);
5493          } else {
5494              $thing = isset($replace) ? str_replace($trim, $replace, $thing) : trim($thing, $trim);
5495          }
5496      }
5497  
5498      $thing = $wraptag && trim($thing) !== '' ? doTag($thing, $wraptag, $class, '', '', $html_id) : $thing;
5499  
5500      return $label && trim($thing) !== '' ? doLabel($label, $labeltag).n.$thing : $thing;
5501  }

title

Description

title

Description

title

Description

title

title

Body