Textpattern PHP Cross Reference Content Management Systems

Source: /textpattern/lib/txplib_html.php - 1917 lines - 53544 bytes - Summary - Text - Print

Description: Collection of HTML widgets.

   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 HTML widgets.
  26   *
  27   * @package HTML
  28   */
  29  
  30  /**
  31   * Renders the admin-side footer.
  32   *
  33   * The footer's default markup is provided by a theme. It can be further
  34   * customised via the "admin_side > footer" pluggable UI callback event.
  35   *
  36   * In addition to the pluggable UI, this function also calls callback events
  37   * "admin_side > main_content_end" and "admin_side > body_end".
  38   */
  39  
  40  function end_page()
  41  {
  42      global $event, $app_mode, $theme, $textarray_script;
  43  
  44      if ($app_mode != 'async' && $event != 'tag') {
  45          callback_event('admin_side', 'main_content_end');
  46          echo n.'</main><!-- /txp-body -->'.n.'<footer class="txp-footer">';
  47          echo pluggable_ui('admin_side', 'footer', $theme->footer());
  48          callback_event('admin_side', 'body_end');
  49          echo script_js('vendors/PrismJS/prism/prism.js', TEXTPATTERN_SCRIPT_URL).
  50              script_js('textpattern.textarray = '.json_encode($textarray_script, TEXTPATTERN_JSON), true).
  51              n.'</footer><!-- /txp-footer -->'.n.'</body>'.n.'</html>';
  52      }
  53  }
  54  
  55  /**
  56   * Renders the user interface for one head cell of columnar data.
  57   *
  58   * @param  string $value   Element text
  59   * @param  string $sort    Sort criterion
  60   * @param  string $event   Event name
  61   * @param  bool   $is_link Include link to admin action in user interface according to the other params
  62   * @param  string $dir     Sort direction, either "asc" or "desc"
  63   * @param  string $crit    Search criterion
  64   * @param  string $method  Search method
  65   * @param  string $class   HTML "class" attribute applied to the resulting element
  66   * @param  string $step    Step name
  67   * @return string HTML
  68   */
  69  
  70  function column_head($value, $sort = '', $event = '', $is_link = '', $dir = '', $crit = '', $method = '', $class = '', $step = 'list')
  71  {
  72      if (is_array($value)) {
  73          extract($value);
  74      }
  75  
  76      $options = (isset($options) ? (array) $options : array()) + array(
  77          'class'    => $class,
  78          'data-col' => $sort,
  79      );
  80  
  81      $head_items = array(
  82          'value'   => $value,
  83          'sort'    => $sort,
  84          'event'   => $event,
  85          'step'    => $step,
  86          'is_link' => $is_link,
  87          'dir'     => $dir,
  88          'crit'    => $crit,
  89          'method'  => $method,
  90      );
  91  
  92      return column_multi_head(array($head_items), $options);
  93  }
  94  
  95  /**
  96   * Renders the user interface for multiple head cells of columnar data.
  97   *
  98   * @param  array  $head_items An array of hashed elements. Valid keys: 'value', 'sort', 'event', 'is_link', 'dir', 'crit', 'method'
  99   * @param  string $class      HTML "class" attribute applied to the resulting element
 100   * @return string HTML
 101   */
 102  
 103  function column_multi_head($head_items, $class = '')
 104  {
 105      $o = '';
 106      $first_item = true;
 107  
 108      foreach ($head_items as $item) {
 109          if (empty($item)) {
 110              continue;
 111          }
 112  
 113          extract(lAtts(array(
 114              'value'   => '',
 115              'sort'    => '',
 116              'event'   => '',
 117              'step'    => 'list',
 118              'is_link' => '',
 119              'dir'     => '',
 120              'crit'    => '',
 121              'method'  => '',
 122          ), $item));
 123  
 124          $o .= ($first_item) ? '' : ', ';
 125          $first_item = false;
 126  
 127          if ($is_link) {
 128              $o .= href(gTxt($value), array(
 129                  'event'         => $event,
 130                  'step'          => $step,
 131                  'sort'          => $sort,
 132                  'dir'           => $dir,
 133                  'crit'          => $crit,
 134                  'search_method' => $method,
 135              ), array());
 136          } else {
 137              $o .= gTxt($value);
 138          }
 139      }
 140  
 141      $extra_atts = is_array($class) ? $class : array('class' => $class);
 142  
 143      return hCell($o, '', $extra_atts + array('scope' => 'col'));
 144  }
 145  
 146  /**
 147   * Renders a &lt;th&gt; element.
 148   *
 149   * @param  string       $text    Cell text
 150   * @param  string       $caption Is not used
 151   * @param  string|array $atts    HTML attributes
 152   * @return string HTML
 153   */
 154  
 155  function hCell($text = '', $caption = '', $atts = '')
 156  {
 157      $text = ('' === $text) ? sp : $text;
 158  
 159      return n.tag($text, 'th', $atts);
 160  }
 161  
 162  /**
 163   * Renders a link invoking an admin-side action.
 164   *
 165   * @param  string $event    Event
 166   * @param  string $step     Step
 167   * @param  string $linktext Link text
 168   * @param  string $class    HTML class attribute for link
 169   * @return string HTML
 170   */
 171  
 172  function sLink($event, $step, $linktext, $class = '')
 173  {
 174      if ($linktext === '') {
 175          $linktext = null;
 176      }
 177  
 178      return href($linktext, array(
 179          'event' => $event,
 180          'step'  => $step,
 181      ), array('class' => $class));
 182  }
 183  
 184  /**
 185   * Renders a link with two additional URL parameters.
 186   *
 187   * Renders a link invoking an admin-side action while taking up to two
 188   * additional URL parameters.
 189   *
 190   * @param  string $event    Event
 191   * @param  string $step     Step
 192   * @param  string $thing    URL parameter key #1
 193   * @param  string $value    URL parameter value #1
 194   * @param  string $linktext Link text
 195   * @param  string $thing2   URL parameter key #2
 196   * @param  string $val2     URL parameter value #2
 197   * @param  string $title    Anchor title
 198   * @param  string $class    HTML class attribute
 199   * @return string HTML
 200   */
 201  
 202  function eLink($event, $step, $thing, $value, $linktext, $thing2 = '', $val2 = '', $title = '', $class = '')
 203  {
 204      if ($title) {
 205          $title = gTxt($title);
 206      }
 207  
 208      if ($linktext === '') {
 209          $linktext = null;
 210      } else {
 211          $linktext = escape_title($linktext);
 212      }
 213  
 214      return href($linktext, array(
 215          'event'      => $event,
 216          'step'       => $step,
 217          $thing       => $value,
 218          $thing2      => $val2,
 219          '_txp_token' => form_token(),
 220      ), array(
 221          'class' => $class,
 222          'title' => $title,
 223      ));
 224  }
 225  
 226  /**
 227   * Renders a link with one additional URL parameter.
 228   *
 229   * Renders an link invoking an admin-side action while taking up to one
 230   * additional URL parameter.
 231   *
 232   * @param  string $event Event
 233   * @param  string $step  Step
 234   * @param  string $thing URL parameter key
 235   * @param  string $value URL parameter value
 236   * @param  string $class HTML class attribute
 237   * @return string HTML
 238   */
 239  
 240  function wLink($event, $step = '', $thing = '', $value = '', $class = '')
 241  {
 242      return href(sp.'!'.sp, array(
 243          'event'      => $event,
 244          'step'       => $step,
 245          $thing       => $value,
 246          '_txp_token' => form_token(),
 247      ), array('class' => $class));
 248  }
 249  
 250  /**
 251   * Renders a delete link.
 252   *
 253   * Renders a link invoking an admin-side "delete" action while taking up to two
 254   * additional URL parameters.
 255   *
 256   * @param  string $event     Event
 257   * @param  string $step      Step
 258   * @param  string $thing     URL parameter key #1
 259   * @param  string $value     URL parameter value #1
 260   * @param  string $verify    Show an "Are you sure?" dialogue with this text
 261   * @param  string $thing2    URL parameter key #2
 262   * @param  string $thing2val URL parameter value #2
 263   * @param  bool   $get       If TRUE, uses GET request
 264   * @param  array  $remember  Convey URL parameters for page state. Member sequence is $page, $sort, $dir, $crit, $search_method
 265   * @return string HTML
 266   */
 267  
 268  function dLink($event, $step, $thing, $value, $verify = '', $thing2 = '', $thing2val = '', $get = '', $remember = null)
 269  {
 270      if ($remember) {
 271          list($page, $sort, $dir, $crit, $search_method) = $remember;
 272      }
 273  
 274      if ($get) {
 275          if ($verify) {
 276              $verify = gTxt($verify);
 277          } else {
 278              $verify = gTxt('confirm_delete_popup');
 279          }
 280  
 281          if ($remember) {
 282              return href(gTxt('delete'), array(
 283                  'event'         => $event,
 284                  'step'          => $step,
 285                  $thing          => $value,
 286                  $thing2         => $thing2val,
 287                  '_txp_token'    => form_token(),
 288                  'page'          => $page,
 289                  'sort'          => $sort,
 290                  'dir'           => $dir,
 291                  'crit'          => $crit,
 292                  'search_method' => $search_method,
 293              ), array(
 294                  'class'       => 'destroy ui-icon ui-icon-close',
 295                  'title'       => gTxt('delete'),
 296                  'data-verify' => $verify,
 297              ));
 298          }
 299  
 300          return href(gTxt('delete'), array(
 301              'event'      => $event,
 302              'step'       => $step,
 303              $thing       => $value,
 304              $thing2      => $thing2val,
 305              '_txp_token' => form_token(),
 306          ), array(
 307              'class'       => 'destroy ui-icon ui-icon-close',
 308              'title'       => gTxt('delete'),
 309              'data-verify' => $verify,
 310          ));
 311      }
 312  
 313      return join('', array(
 314          n.'<form method="post" action="index.php" data-verify="'.gTxt('confirm_delete_popup').'">',
 315          tag(
 316              span(gTxt('delete'), array('class' => 'ui-icon ui-icon-close')),
 317              'button',
 318              array(
 319                  'class'      => 'destroy',
 320                  'type'       => 'submit',
 321                  'title'      => gTxt('delete'),
 322                  'aria-label' => gTxt('delete'),
 323              )
 324          ),
 325          eInput($event).
 326          sInput($step),
 327          hInput($thing, $value),
 328          ($thing2) ? hInput($thing2, $thing2val) : '',
 329          ($remember) ? hInput(compact('page', 'sort', 'dir', 'crit', 'search_method')) : '',
 330          tInput(),
 331          n.'</form>',
 332      ));
 333  }
 334  
 335  /**
 336   * Renders an add link.
 337   *
 338   * This function can be used for invoking an admin-side "add" action while
 339   * taking up to two additional URL parameters.
 340   *
 341   * @param  string $event  Event
 342   * @param  string $step   Step
 343   * @param  string $thing  URL parameter key #1
 344   * @param  string $value  URL parameter value #1
 345   * @param  string $thing2 URL parameter key #2
 346   * @param  string $value2 URL parameter value #2
 347   * @return string HTML
 348   */
 349  
 350  function aLink($event, $step, $thing = '', $value = '', $thing2 = '', $value2 = '')
 351  {
 352      return href('+', array(
 353          'event'      => $event,
 354          'step'       => $step,
 355          $thing       => $value,
 356          $thing2      => $value2,
 357          '_txp_token' => form_token(),
 358      ), array('class' => 'alink'));
 359  }
 360  
 361  /**
 362   * Renders a link invoking an admin-side "previous/next article" action.
 363   *
 364   * @param  string $name  Link text
 365   * @param  string $event Event
 366   * @param  string $step  Step
 367   * @param  int    $id    ID of target Textpattern object (article,...)
 368   * @param  string $title HTML title attribute
 369   * @param  string $rel   HTML rel attribute
 370   * @return string HTML
 371   */
 372  
 373  function prevnext_link($name, $event, $step, $id, $title = '', $rel = '')
 374  {
 375      return href($name, array(
 376          'event' => $event,
 377          'step'  => $step,
 378          'ID'    => $id,
 379      ), array(
 380          'class' => 'navlink',
 381          'title' => $title,
 382          'rel'   => $rel,
 383      ));
 384  }
 385  
 386  /**
 387   * Renders a link invoking an admin-side "previous/next page" action.
 388   *
 389   * @param  string $event         Event
 390   * @param  int    $page          Target page number
 391   * @param  string $label         Link text
 392   * @param  string $type          Direction, either "prev" or "next"
 393   * @param  string $sort          Sort field
 394   * @param  string $dir           Sort direction, either "asc" or "desc"
 395   * @param  string $crit          Search criterion
 396   * @param  string $search_method Search method
 397   * @param  string $step          Step
 398   * @return string HTML
 399   */
 400  
 401  function PrevNextLink($event, $page, $label, $type, $sort = '', $dir = '', $crit = '', $search_method = '', $step = 'list')
 402  {
 403      $theClass = ($type === 'next') ? 'ui-icon-arrowthick-1-e' : 'ui-icon-arrowthick-1-w';
 404  
 405      return href(
 406          span(
 407              $label,
 408              array('class' => 'ui-icon '.$theClass)
 409          ),
 410          array(
 411              'event'         => $event,
 412              'step'          => $step,
 413              'page'          => (int) $page,
 414              'dir'           => $dir,
 415              'crit'          => $crit,
 416              'search_method' => $search_method,
 417          ),
 418          array(
 419              'rel'        => $type,
 420              'title'      => $label,
 421              'aria-label' => $label,
 422          )
 423      );
 424  }
 425  
 426  /**
 427   * Renders a page navigation form.
 428   *
 429   * @param  string $event         Event
 430   * @param  int    $page          Current page number
 431   * @param  int    $numPages         Total pages
 432   * @param  string $sort          Sort criterion
 433   * @param  string $dir           Sort direction, either "asc" or "desc"
 434   * @param  string $crit          Search criterion
 435   * @param  string $search_method Search method
 436   * @param  int    $total         Total search term hit count [0]
 437   * @param  int    $limit         First visible search term hit number [0]
 438   * @param  string $step             Step
 439   * @param  int    $list          Number of displayed page links discounting jump links, previous and next
 440   * @return string HTML
 441   */
 442  
 443  function nav_form($event, $page, $numPages, $sort = '', $dir = '', $crit = '', $search_method = '', $total = 0, $limit = 0, $step = 'list', $list = 5)
 444  {
 445      $out = array();
 446  
 447      if ($numPages > 1 && $crit !== '') {
 448          $out[] = announce(
 449              gTxt('showing_search_results', array(
 450                  '{from}'  => (($page - 1) * $limit) + 1,
 451                  '{to}'    => min($total, $page * $limit),
 452                  '{total}' => $total,
 453              )),
 454              TEXTPATTERN_ANNOUNCE_REGULAR
 455          );
 456      }
 457  
 458      $nav = array();
 459      $list--;
 460      $page = max(min($page, $numPages), 1);
 461  
 462      $parameters = array(
 463          'event'         => $event,
 464          'step'          => $step,
 465          'dir'           => $dir,
 466          'crit'          => $crit,
 467          'search_method' => $search_method,
 468      );
 469  
 470      // Previous page.
 471      if ($page > 1) {
 472          $nav[] = n.PrevNextLink($event, $page - 1, gTxt('prev'), 'prev', $sort, $dir, $crit, $search_method, $step);
 473      } else {
 474          $nav[] = n.span(
 475              span(gTxt('prev'), array('class' => 'ui-icon ui-icon-arrowthick-1-w')),
 476              array(
 477                  'class'         => 'disabled',
 478                  'aria-disabled' => 'true',
 479                  'aria-label'    => gTxt('prev'),
 480              )
 481          );
 482      }
 483  
 484  
 485      $nav[] = form(
 486          n.tag(gTxt('page'), 'label', array('for' => 'current-page')).
 487          n.tag_void('input', array(
 488              'class'     => 'current-page',
 489              'id'        => 'current-page',
 490              'name'      => 'page',
 491              'type'      => 'text',
 492              'size'      => INPUT_XSMALL,
 493              'inputmode' => 'numeric',
 494              'pattern'   => '[0-9]+',
 495              'value'     => $page,
 496          )).
 497          n.gTxt('of').
 498          n.span($numPages, array('class' => 'total-pages')).
 499          eInput($event).
 500          hInput(compact('sort', 'dir', 'crit', 'search_method')),
 501          '',
 502          '',
 503          'get'
 504      );
 505  
 506      // Next page.
 507      if ($page < $numPages) {
 508          $nav[] = n.PrevNextLink($event, $page + 1, gTxt('next'), 'next', $sort, $dir, $crit, $search_method, $step);
 509      } else {
 510          $nav[] = n.span(
 511              span(gTxt('next'), array('class' => 'ui-icon ui-icon-arrowthick-1-e')),
 512              array(
 513                  'class'         => 'disabled',
 514                  'aria-disabled' => 'true',
 515                  'aria-label'    => gTxt('next'),
 516              )
 517          );
 518      }
 519  
 520      $out[] = n.tag(join($nav).n, 'nav', array(
 521          'class'      => 'prev-next',
 522          'aria-label' => gTxt('page_nav'),
 523          'style'      => ($numPages > 1 ? false : 'display:none'),
 524      ));
 525  
 526      return join('', $out);
 527  }
 528  
 529  /**
 530   * Wraps a collapsible region and group structure around content.
 531   *
 532   * @param  string $id        HTML id attribute for the region wrapper and ARIA label
 533   * @param  string $content   Content to wrap. If empty, only the outer wrapper will be rendered
 534   * @param  string $anchor_id HTML id attribute for the collapsible wrapper
 535   * @param  string $label     L10n label name
 536   * @param  string $pane      Pane reference for maintaining toggle state in prefs. Prefixed with 'pane_', suffixed with '_visible'
 537   * @param  string $class     CSS class name to apply to wrapper
 538   * @param  string $help      Help text item
 539   * @return string HTML
 540   * @since  4.6.0
 541   */
 542  
 543  function wrapRegion($id, $content = '', $anchor_id = '', $label = '', $pane = '', $class = '', $help = '', $visible = null)
 544  {
 545      global $event;
 546      $label = $label ? gTxt($label) : null;
 547  
 548      if ($anchor_id && $pane) {
 549          $heading_class = 'txp-summary'.($visible ? ' expanded' : '');
 550          $display_state = array(
 551              'class' => 'toggle',
 552              'id'    => $anchor_id,
 553              'role'  => 'group',
 554              'style' => $visible ? '' : 'display:none',
 555          );
 556  
 557          $label = href($label, '#'.$anchor_id, array(
 558              'role'           => 'button',
 559              'data-txp-token' => md5($pane.$event.form_token().get_pref('blog_uid')),
 560              'data-txp-pane'  => $pane,
 561          ));
 562  
 563          $help = '';
 564      } else {
 565          $heading_class = '';
 566          $display_state = array('role' => 'group');
 567      }
 568  
 569      if ($content) {
 570          $content =
 571              hed($label.popHelp($help), 3, array(
 572                  'class' => $heading_class,
 573                  'id'    => $id.'-label',
 574              )).
 575              n.tag($content.n, 'div', $display_state).n;
 576      }
 577  
 578      return n.tag($content, 'section', array(
 579          'class'           => trim('txp-details '.$class),
 580          'id'              => $id,
 581          'aria-labelledby' => $content ? $id.'-label' : '',
 582      ));
 583  }
 584  
 585  /**
 586   * Wraps a region and group structure around content.
 587   *
 588   * @param  string $name    HTML id attribute for the group wrapper and ARIA label
 589   * @param  string $content Content to wrap
 590   * @param  string $label   L10n label name
 591   * @param  string $class   CSS class name to apply to wrapper
 592   * @param  string $help    Help text item
 593   * @return string HTML
 594   * @see    wrapRegion()
 595   * @since  4.6.0
 596   */
 597  
 598  function wrapGroup($id, $content, $label, $class = '', $help = '')
 599  {
 600      return wrapRegion($id, $content, '', $label, '', $class, $help);
 601  }
 602  
 603  /**
 604   * Renders start of a layout &lt;table&gt; element.
 605   *
 606   * @param  string $id    HTML id attribute
 607   * @param  string $align HTML align attribute
 608   * @param  string $class HTML class attribute
 609   * @param  int    $p     HTML cellpadding attribute
 610   * @param  int    $w     HTML width attribute
 611   * @return string HTML
 612   * @example
 613   * startTable().
 614   * tr(td('column') . td('column')).
 615   * tr(td('column') . td('column')).
 616   * endTable();
 617   */
 618  
 619  function startTable($id = '', $align = '', $class = '', $p = 0, $w = 0)
 620  {
 621      $atts = join_atts(array(
 622          'class'       => $class,
 623          'id'          => $id,
 624          'cellpadding' => (int) $p,
 625          'width'       => (int) $w,
 626          'align'       => $align,
 627      ), TEXTPATTERN_STRIP_EMPTY);
 628  
 629      return n.'<table'.$atts.'>';
 630  }
 631  
 632  /**
 633   * Renders closing &lt;/table&gt; tag.
 634   *
 635   * @return string HTML
 636   */
 637  
 638  function endTable()
 639  {
 640      return n.'</table>';
 641  }
 642  
 643  /**
 644   * Renders &lt;tr&gt; elements from input parameters.
 645   *
 646   * Takes a list of arguments containing each making a row.
 647   *
 648   * @return string HTML
 649   * @example
 650   * stackRows(
 651   *     td('cell') . td('cell'),
 652   *  td('cell') . td('cell')
 653   * );
 654   */
 655  
 656  function stackRows()
 657  {
 658      foreach (func_get_args() as $a) {
 659          $o[] = tr($a);
 660      }
 661  
 662      return join('', $o);
 663  }
 664  
 665  /**
 666   * Renders a &lt;td&gt; element.
 667   *
 668   * @param  string $content Cell content
 669   * @param  int    $width   HTML width attribute
 670   * @param  string $class   HTML class attribute
 671   * @param  string $id      HTML id attribute
 672   * @return string HTML
 673   */
 674  
 675  function td($content = '', $width = null, $class = '', $id = '')
 676  {
 677      $opts = array(
 678          'class' => $class,
 679          'id'    => $id,
 680      );
 681  
 682      if (is_numeric($width)) {
 683          $opts['width'] = (int) $width;
 684      } elseif (is_array($width)) {
 685          $opts = array_merge($opts, $width);
 686      }
 687  
 688      return tda($content, $opts);
 689  }
 690  
 691  /**
 692   * Renders a &lt;td&gt; element with attributes.
 693   *
 694   * @param  string       $content Cell content
 695   * @param  string|array $atts    Cell attributes
 696   * @return string HTML
 697   */
 698  
 699  function tda($content, $atts = '')
 700  {
 701      $content = ($content === '') ? sp : $content;
 702  
 703      return n.tag($content, 'td', $atts);
 704  }
 705  
 706  /**
 707   * Renders a &lt;td&gt; element with attributes.
 708   *
 709   * This function is identical to tda().
 710   *
 711   * @param  string       $content Cell content
 712   * @param  string|array $atts    Cell attributes
 713   * @return string HTML
 714   * @access private
 715   * @see    tda()
 716   */
 717  
 718  function tdtl($content, $atts = '')
 719  {
 720      return tda($content, $atts);
 721  }
 722  
 723  /**
 724   * Renders a &lt;tr&gt; element with attributes.
 725   *
 726   * @param  string       $content Row content
 727   * @param  string|array $atts    Row attributes
 728   * @return string HTML
 729   */
 730  
 731  function tr($content, $atts = '')
 732  {
 733      return n.tag($content, 'tr', $atts);
 734  }
 735  
 736  /**
 737   * Renders a &lt;td&gt; element with top/left text orientation, colspan and
 738   * other attributes.
 739   *
 740   * @param  string $content Cell content
 741   * @param  int    $span    Cell colspan attribute
 742   * @param  int    $width   Cell width attribute
 743   * @param  string $class   Cell class attribute
 744   * @return string HTML
 745   */
 746  
 747  function tdcs($content, $span, $width = null, $class = '')
 748  {
 749      $opts = array(
 750          'class'   => $class,
 751          'colspan' => (int) $span,
 752      );
 753  
 754      if (is_numeric($width)) {
 755          $opts['width'] = (int) $width;
 756      }
 757  
 758      return tda($content, $opts);
 759  }
 760  
 761  /**
 762   * Renders a &lt;td&gt; element with a rowspan attribute.
 763   *
 764   * @param  string $content Cell content
 765   * @param  int    $span    Cell rowspan attribute
 766   * @param  int    $width   Cell width attribute
 767   * @param  string $class   Cell class attribute
 768   * @return string HTML
 769   */
 770  
 771  function tdrs($content, $span, $width = null, $class = '')
 772  {
 773      $opts = array(
 774          'class'   => $class,
 775          'rowspan' => (int) $span,
 776      );
 777  
 778      if (is_numeric($width)) {
 779          $opts['width'] = (int) $width;
 780      }
 781  
 782      return tda($content, $opts);
 783  }
 784  
 785  /**
 786   * Renders a form label inside a table cell.
 787   *
 788   * @param  string $text     Label text
 789   * @param  string $help     Help text
 790   * @param  string $label_id HTML "for" attribute, i.e. id of corresponding form element
 791   * @return string HTML
 792   */
 793  
 794  function fLabelCell($text, $help = '', $label_id = '')
 795  {
 796      $cell = gTxt($text).' '.popHelp($help);
 797  
 798      if ($label_id) {
 799          $cell = tag($cell, 'label', array('for' => $label_id));
 800      }
 801  
 802      return tda($cell, array('class' => 'cell-label'));
 803  }
 804  
 805  /**
 806   * Renders a form input inside a table cell.
 807   *
 808   * @param  string $name     HTML name attribute
 809   * @param  string $var      Input value
 810   * @param  int    $tabindex HTML tabindex attribute
 811   * @param  int    $size     HTML size attribute
 812   * @param  bool   $help     TRUE to display help link
 813   * @param  string $id       HTML id attribute
 814   * @return string HTML
 815   */
 816  
 817  function fInputCell($name, $var = '', $tabindex = 0, $size = 0, $help = false, $id = '')
 818  {
 819      $pop = ($help) ? popHelp($name) : '';
 820  
 821      return tda(fInput('text', $name, $var, '', '', '', $size, $tabindex, $id).$pop);
 822  }
 823  
 824  /**
 825   * Renders a name-value input control with label.
 826   *
 827   * The rendered input can be customised via the
 828   * '{$event}_ui > inputlabel.{$name}' pluggable UI callback event.
 829   *
 830   * @param  string       $name        Input name
 831   * @param  string       $input       Complete input control widget
 832   * @param  string|array $label       Label text | array (label text, HTML block to append to label)
 833   * @param  string|array $help        Help text item | array(help text item, inline help text)
 834   * @param  string|array $atts        Class name | attribute pairs to assign to container div
 835   * @param  string|array $wraptag_val Tag to wrap the value / label in, or empty to omit
 836   * @return string HTML
 837   * @example
 838   * echo inputLabel('active', yesnoRadio('active'), 'Keep active?');
 839   */
 840  
 841  function inputLabel($name, $input, $label = '', $help = array(), $atts = array(), $wraptag_val = array('div', 'div'))
 842  {
 843      global $event;
 844  
 845      $arguments = compact('name', 'input', 'label', 'help', 'atts', 'wraptag_val');
 846  
 847      $fallback_class = 'txp-form-field edit-'.str_replace('_', '-', $name);
 848      $tools = '';
 849  
 850      if ($atts && is_string($atts)) {
 851          $atts = array('class' => $atts);
 852      } elseif (!$atts) {
 853          $atts = array('class' => $fallback_class);
 854      } elseif (is_array($atts) && !array_key_exists('class', $atts)) {
 855          $atts['class'] = $fallback_class;
 856      }
 857  
 858      if (!is_array($help)) {
 859          $help = array($help);
 860      }
 861  
 862      if (is_array($label)) {
 863          if (isset($label[1])) {
 864              $tools = (string) $label[1];
 865          }
 866  
 867          $label = (string) $label[0];
 868      }
 869  
 870      if (empty($help)) {
 871          $help = array(
 872              0 => '',
 873              1 => '',
 874          );
 875      }
 876  
 877      $inlineHelp = (isset($help[1])) ? $help[1] : '';
 878  
 879      if ($label !== '') {
 880          $labelContent = tag(gTxt($label).popHelp($help[0]), 'label', array('for' => $name)).$tools;
 881      } else {
 882          $labelContent = gTxt($name).popHelp($help[0]).$tools;
 883      }
 884  
 885      if (!is_array($wraptag_val)) {
 886          $wraptag_val = array($wraptag_val, $wraptag_val);
 887      }
 888  
 889      if ($wraptag_val[0]) {
 890          $input = n.tag($input, $wraptag_val[0], array('class' => 'txp-form-field-value'));
 891      }
 892  
 893      if (isset($wraptag_val[1]) && $wraptag_val[1]) {
 894          $labeltag = n.tag($labelContent, $wraptag_val[1], array('class' => 'txp-form-field-label'));
 895      } else {
 896          $labeltag = $labelContent;
 897      }
 898  
 899      $out = n.tag(
 900          $labeltag.
 901          fieldHelp($inlineHelp).
 902          $input.n,
 903          'div',
 904          $atts
 905      );
 906  
 907      return pluggable_ui($event.'_ui', 'inputlabel.'.$name, $out, $arguments);
 908  }
 909  
 910  /**
 911   * Renders anything as an XML element.
 912   *
 913   * @param  string       $content Enclosed content
 914   * @param  string       $tag     The tag without brackets
 915   * @param  string|array $atts    The element's HTML attributes
 916   * @return string HTML
 917   * @example
 918   * echo tag('Link text', 'a', array('href' => '#', 'class' => 'warning'));
 919   */
 920  
 921  function tag($content, $tag, $atts = '')
 922  {
 923      static $tags = array();
 924  
 925      if (empty($tag) || $content === '') {
 926          return $content;
 927      }
 928  
 929      if (!isset($tags[$tag])) {
 930          $tags[$tag] = preg_match('/^\w[\w\-\.\:]*$/', $tag) ? 1 :
 931              (strpos($tag, '<+>') === false ? 2 : 3);
 932      }
 933  
 934      switch ($tags[$tag]) {
 935          case 1:
 936              $atts = $atts ? join_atts($atts) : '';
 937              return '<'.$tag.$atts.'>'.$content.'</'.$tag.'>';
 938          case 2:
 939              return $tag.$content.$tag;
 940          default:
 941              return str_replace('<+>', $content, $tag);
 942      }
 943  }
 944  
 945  /**
 946   * Renders anything as a HTML void element.
 947   *
 948   * @param  string $tag  The tag without brackets
 949   * @param  string|array $atts HTML attributes
 950   * @return string HTML
 951   * @since  4.6.0
 952   * @example
 953   * echo tag_void('input', array('name' => 'name', 'type' => 'text'));
 954   */
 955  
 956  function tag_void($tag, $atts = '')
 957  {
 958      return '<'.$tag.join_atts($atts).' />';
 959  }
 960  
 961  /**
 962   * Renders anything as a HTML start tag.
 963   *
 964   * @param  string $tag The tag without brackets
 965   * @param  string|array $atts HTML attributes
 966   * @return string A HTML start tag
 967   * @since  4.6.0
 968   * @example
 969   * echo tag_start('section', array('class' => 'myClass'));
 970   */
 971  
 972  function tag_start($tag, $atts = '')
 973  {
 974      return '<'.$tag.join_atts($atts).'>';
 975  }
 976  
 977  /**
 978   * Renders anything as a HTML end tag.
 979   *
 980   * @param  string $tag The tag without brackets
 981   * @return string A HTML end tag
 982   * @since  4.6.0
 983   * @example
 984   * echo tag_end('section');
 985   */
 986  
 987  function tag_end($tag)
 988  {
 989      return '</'.$tag.'>';
 990  }
 991  
 992  /**
 993   * Renders a &lt;p&gt; element.
 994   *
 995   * @param  string       $item Enclosed content
 996   * @param  string|array $atts HTML attributes
 997   * @return string HTML
 998   * @example
 999   * echo graf('This a paragraph.');
1000   */
1001  
1002  function graf($item, $atts = '')
1003  {
1004      return n.tag($item, 'p', $atts);
1005  }
1006  
1007  /**
1008   * Renders a &lt;hx&gt; element.
1009   *
1010   * @param  string       $item  The Enclosed content
1011   * @param  int          $level Heading level 1...6
1012   * @param  string|array $atts  HTML attributes
1013   * @return string HTML
1014   * @example
1015   * echo hed('Heading', 2);
1016   */
1017  
1018  function hed($item, $level, $atts = '')
1019  {
1020      return n.tag($item, 'h'.$level, $atts).n;
1021  }
1022  
1023  /**
1024   * Renders an &lt;a&gt; element.
1025   *
1026   * @param  string       $item Enclosed content
1027   * @param  string|array $href The link target
1028   * @param  string|array $atts HTML attributes
1029   * @return string HTML
1030   */
1031  
1032  function href($item, $href, $atts = '')
1033  {
1034      if (is_array($atts)) {
1035          $atts['href'] = $href;
1036      } else {
1037          if (is_array($href)) {
1038              $href = join_qs($href);
1039          }
1040  
1041          $atts .= ' href="'.$href.'"';
1042      }
1043  
1044      return tag($item, 'a', $atts);
1045  }
1046  
1047  /**
1048   * Renders a &lt;strong&gt; element.
1049   *
1050   * @param  string       $item Enclosed content
1051   * @param  string|array $atts HTML attributes
1052   * @return string HTML
1053   */
1054  
1055  function strong($item, $atts = '')
1056  {
1057      return tag($item, 'strong', $atts);
1058  }
1059  
1060  /**
1061   * Renders a &lt;span&gt; element.
1062   *
1063   * @param  string       $item Enclosed content
1064   * @param  string|array $atts HTML attributes
1065   * @return string HTML
1066   */
1067  
1068  function span($item, $atts = '')
1069  {
1070      return tag($item, 'span', $atts);
1071  }
1072  
1073  /**
1074   * Renders a &lt;pre&gt; element.
1075   *
1076   * @param  string       $item The input string
1077   * @param  string|array $atts HTML attributes
1078   * @return string HTML
1079   * @example
1080   * echo htmlPre('&lt;?php echo "Hello World"; ?&gt;');
1081   */
1082  
1083  function htmlPre($item, $atts = '')
1084  {
1085      if (($item = tag($item, 'code')) === '') {
1086          $item = null;
1087      }
1088  
1089      return tag($item, 'pre', $atts);
1090  }
1091  
1092  /**
1093   * Renders a HTML comment (&lt;!-- --&gt;) element.
1094   *
1095   * @param  string $item The input string
1096   * @return string HTML
1097   * @example
1098   * echo comment('Some HTML comment.');
1099   */
1100  
1101  function comment($item)
1102  {
1103      return '<!-- '.str_replace('--', '- - ', $item).' -->';
1104  }
1105  
1106  /**
1107   * Renders a &lt;small&gt element.
1108   *
1109   * @param  string       $item The input string
1110   * @param  string|array $atts HTML attributes
1111   * @return string HTML
1112   */
1113  
1114  function small($item, $atts = '')
1115  {
1116      return tag($item, 'small', $atts);
1117  }
1118  
1119  /**
1120   * Renders a table data row from an array of content => width pairs.
1121   *
1122   * @param  array        $array Array of content => width pairs
1123   * @param  string|array $atts  Table row attributes
1124   * @return string A HTML table row
1125   */
1126  
1127  function assRow($array, $atts = '')
1128  {
1129      $out = array();
1130  
1131      foreach ($array as $value => $width) {
1132          $out[] = tda($value, array('width' => (int) $width));
1133      }
1134  
1135      return tr(join('', $out), $atts);
1136  }
1137  
1138  /**
1139   * Renders a table head row from an array of strings.
1140   *
1141   * Takes an argument list of head text strings. i18n is applied to the strings.
1142   *
1143   * @return string HTML
1144   */
1145  
1146  function assHead()
1147  {
1148      $array = func_get_args();
1149      $o = array();
1150  
1151      foreach ($array as $a) {
1152          $o[] = hCell(gTxt($a), '', ' scope="col"');
1153      }
1154  
1155      return tr(join('', $o));
1156  }
1157  
1158  /**
1159   * Renders the ubiquitous popup help button.
1160   *
1161   * The rendered link can be customised via a 'admin_help > {$help_var}'
1162   * pluggable UI callback event.
1163   *
1164   * @param  string|array $help_var Help topic or topic and lang in an array
1165   * @param  int          $width    Popup window width
1166   * @param  int          $height   Popup window height
1167   * @param  string       $class    HTML class
1168   * @param  string       $inline   Inline pophelp
1169   * @return string       HTML
1170   */
1171  
1172  function popHelp($help_var, $width = 0, $height = 0, $class = 'pophelp', $inline = '')
1173  {
1174      global $txp_user, $prefs;
1175  
1176      $lang = null;
1177  
1178      if (empty($help_var) || empty($prefs['module_pophelp'])) {
1179          return '';
1180      }
1181  
1182      if (is_array($help_var)) {
1183          $lang = empty($help_var[1]) ? null : $help_var[1];
1184          $help_var = $help_var[0];
1185      }
1186  
1187      $url = filter_var($help_var, FILTER_VALIDATE_URL);
1188  
1189      $atts = array(
1190          'rel'    => 'help',
1191          'title'  => gTxt('help'),
1192          'role'   => 'button',
1193      );
1194  
1195      if ($url === false) {
1196          $atts['class'] = $class;
1197          $url = '#';
1198          if (! empty($inline)) {
1199              $atts['data-item'] = $inline;
1200          } elseif (empty($txp_user)) {
1201              // Use inline pophelp, if unauthorized user or setup stage
1202              if (class_exists('\Textpattern\Module\Help\HelpAdmin')) {
1203                  $atts['data-item'] = \Txp::get('\Textpattern\Module\Help\HelpAdmin')->pophelp($help_var, $lang);
1204              }
1205          } else {
1206              $url = '?event=help&step=pophelp&item='.urlencode($help_var);
1207          }
1208          $ui = sp.href(span(gTxt('help'), array('class' => 'ui-icon ui-icon-help')), $url, $atts);
1209      } else {
1210          $atts['target'] = '_blank';
1211          $ui = sp.href(span(gTxt('help'), array('class' => 'ui-icon ui-icon-extlink')), $url, $atts);
1212      }
1213  
1214      return pluggable_ui('admin_help', $help_var, $ui, compact('help_var', 'width', 'height', 'class', 'inline'));
1215  }
1216  
1217  /**
1218   * Renders inline help text.
1219   *
1220   * The help topic is the name of a string that can be found in txp_lang.
1221   *
1222   * The rendered link can be customised via a 'admin_help_field > {$help_var}'
1223   * pluggable UI callback event.
1224   *
1225   * @param  string $help_var   Help topic
1226   * @return string HTML
1227   */
1228  
1229  function fieldHelp($help_var)
1230  {
1231      if (!$help_var) {
1232          return '';
1233      }
1234  
1235      $help_text = gTxt($help_var);
1236  
1237      // If rendered string is the same as the input string, either the l10n
1238      // doesn't exist or the string is missing from txp_lang.
1239      // Either way, no instruction text, no render.
1240      if ($help_var === $help_text) {
1241          return '';
1242      }
1243  
1244      $ui = n.tag($help_text, 'div', array('class' => 'txp-form-field-instructions'));
1245  
1246      return pluggable_ui('admin_help_field', $help_var, $ui, compact('help_var'));
1247  }
1248  
1249  /**
1250   * Renders the ubiquitous popup help button with a little less visual noise.
1251   *
1252   * The rendered link can be customised via a 'admin_help > {$help_var}'
1253   * pluggable UI callback event.
1254   *
1255   * @param  string $help_var Help topic
1256   * @param  int    $width    Popup window width
1257   * @param  int    $height   Popup window height
1258   * @return string HTML
1259   */
1260  
1261  function popHelpSubtle($help_var, $width = 0, $height = 0)
1262  {
1263      return popHelp($help_var, $width, $height, 'pophelpsubtle');
1264  }
1265  
1266  /**
1267   * Renders a link that opens a popup tag area.
1268   *
1269   * @param  string $var   Tag name
1270   * @param  string $text  Link text
1271   * @param  array  $atts  Attributes to add to the link
1272   * @return string HTML
1273   */
1274  
1275  function popTag($var, $text, $atts = array())
1276  {
1277      $opts = array(
1278          'event'    => 'tag',
1279          'tag_name' => $var,
1280      ) + $atts;
1281  
1282      return href($text, $opts, array('class' => 'txp-tagbuilder-link'));
1283  }
1284  
1285  /**
1286   * Renders an admin-side message text.
1287   *
1288   * @param  string $thing    Subject
1289   * @param  string $thething Predicate (strong)
1290   * @param  string $action   Object
1291   * @return string HTML
1292   * @deprecated in 4.7.0
1293   */
1294  
1295  function messenger($thing, $thething = '', $action = '')
1296  {
1297      return gTxt($thing).' '.strong($thething).' '.gTxt($action);
1298  }
1299  
1300  /**
1301   * Renders a multi-edit form listing editing methods.
1302   *
1303   * @param  array   $options       array('value' => array( 'label' => '', 'html' => '' ),...)
1304   * @param  string  $event         Event
1305   * @param  string  $step          Step
1306   * @param  int     $page          Page number
1307   * @param  string  $sort          Column sorted by
1308   * @param  string  $dir           Sorting direction
1309   * @param  string  $crit          Search criterion
1310   * @param  string  $search_method Search method
1311   * @return string  HTML
1312   * @example
1313   * echo form(
1314   *     multi_edit(array(
1315   *         'feature' => array('label' => 'Feature', 'html' => yesnoRadio('is_featured', 1)),
1316   *         'delete'  => array('label' => 'Delete'),
1317   *     ))
1318   * );
1319   */
1320  
1321  function multi_edit($options, $event = null, $step = null, $page = '', $sort = '', $dir = '', $crit = '', $search_method = '')
1322  {
1323      $html = $methods = array();
1324      $methods[''] = gTxt('with_selected_option', array('{count}' => '0'));
1325  
1326      if ($event === null) {
1327          global $event;
1328      }
1329  
1330      if ($step === null) {
1331          $step = $event.'_multi_edit';
1332      }
1333  
1334      callback_event_ref($event.'_ui', 'multi_edit_options', 0, $options);
1335  
1336      foreach ($options as $value => $option) {
1337          if (is_array($option)) {
1338              $methods[$value] = $option['label'];
1339  
1340              if (isset($option['html'])) {
1341                  $html[$value] = n.tag($option['html'], 'div', array(
1342                      'class'             => 'multi-option',
1343                      'data-multi-option' => $value,
1344                  ));
1345              }
1346          } else {
1347              $methods[$value] = $option;
1348          }
1349      }
1350  
1351      return n.tag(
1352          selectInput('edit_method', $methods, '').
1353          eInput($event).
1354          sInput($step).
1355          hInput('page', $page).
1356          ($sort ? hInput('sort', $sort).hInput('dir', $dir) : '').
1357          ($crit !== '' ? hInput('crit', $crit).hInput('search_method', $search_method) : '').
1358          join('', $html).
1359          fInput('submit', '', gTxt('go')),
1360          'div',
1361          array('class' => 'multi-edit')
1362      );
1363  }
1364  
1365  /**
1366   * Renders a widget to select various amounts to page lists by.
1367   *
1368   * The rendered options can be changed via a '{$event}_ui > pageby_values'
1369   * callback event.
1370   *
1371   * @param  string      $event Event
1372   * @param  int         $val   Current setting
1373   * @param  string|null $step  Step
1374   * @return string HTML
1375   * @deprecated in 4.7.0
1376   */
1377  
1378  function pageby_form($event, $val, $step = null)
1379  {
1380      return Txp::get('\Textpattern\Admin\Paginator', $event, $step)->render($val);
1381  }
1382  
1383  /**
1384   * Renders an upload form.
1385   *
1386   * The rendered form can be customised via the '{$event}_ui > upload_form'
1387   * pluggable UI callback event.
1388   *
1389   * @param  string       $label         File name label. May be empty
1390   * @param  string       $pophelp       Help item
1391   * @param  string       $step          Step
1392   * @param  string       $event         Event
1393   * @param  string       $id            File id
1394   * @param  int          $max_file_size Maximum allowed file size
1395   * @param  string       $label_id      HTML id attribute for the filename input element
1396   * @param  string       $class         HTML class attribute for the form element
1397   * @param  string|array $wraptag_val   Tag to wrap the value / label in, or empty to omit
1398   * @param  array        $extra         array('postinput' => $categories ...)
1399   * @param  string|array $accept        Comma separated list of allowed file types, or empty to omit
1400   * @return string HTML
1401   */
1402  
1403  function upload_form($label, $pophelp, $step, $event, $id = '', $max_file_size = 1000000, $label_id = '', $class = '', $wraptag_val = array('div', 'div'), $extra = null, $accept = '')
1404  {
1405      if (!$label_id) {
1406          $label_id = $event.'-upload';
1407      }
1408  
1409      if ($wraptag_val) {
1410          $wraptag_class = 'txp-form-field file-uploader';
1411      } else {
1412          $wraptag_class = 'inline-file-uploader';
1413      }
1414  
1415      if ($multiple = (bool) preg_match('/^.+\[\]$/', $step)) {
1416          $step = substr($step, 0, -2);
1417      }
1418  
1419      $name = 'thefile'.($multiple ? '[]' : '');
1420      $argv = func_get_args();
1421  
1422      return pluggable_ui(
1423          $event.'_ui',
1424          'upload_form',
1425          n.tag(
1426              (!empty($max_file_size) ? hInput('MAX_FILE_SIZE', $max_file_size) : '').
1427              eInput($event).
1428              sInput($step).
1429              hInput('id', $id).
1430              tInput().
1431              inputLabel(
1432                  $label_id,
1433                  n.tag_void('input', array(
1434                      'name'     => $name,
1435                      'type'     => 'file',
1436                      'required' => true,
1437                      'id'       => $label_id,
1438                      'multiple' => $multiple,
1439                      'accept'   => $accept,
1440                  )).
1441                  (isset($extra['postinput']) ? $extra['postinput'] : '').
1442                  n.tag(
1443                      fInput('reset', '', gTxt('reset')).
1444                      fInput('submit', '', gTxt('upload')).n,
1445                      'span',
1446                      array('class' => 'inline-file-uploader-actions')
1447                  ),
1448                  $label,
1449                  array($pophelp, 'instructions_'.$pophelp),
1450                  $wraptag_class,
1451                  $wraptag_val
1452              ).
1453              tag(null, 'progress', array(
1454                  'class' => 'txp-upload-progress',
1455                  'style' =>  'display:none',
1456              )),
1457              'form',
1458              array(
1459                  'class'   => 'upload-form'.($class ? ' '.trim($class) : ''),
1460                  'method'  => 'post',
1461                  'enctype' => 'multipart/form-data',
1462                  'action'  => "index.php?event=$event&step=$step",
1463              )
1464          ),
1465          $argv
1466      );
1467  }
1468  
1469  /**
1470   * Renders an admin-side search form.
1471   *
1472   * @param  string $event          Event
1473   * @param  string $step           Step
1474   * @param  string $crit           Search criterion
1475   * @param  array  $methods        Valid search methods
1476   * @param  string $method         Actual search method
1477   * @param  string $default_method Default search method
1478   * @return string HTML
1479   */
1480  
1481  function search_form($event, $step, $crit, $methods, $method, $default_method)
1482  {
1483      $method = ($method) ? $method : $default_method;
1484  
1485      return form(
1486          graf(
1487              tag(gTxt('search'), 'label', array('for' => $event.'-search')).
1488              selectInput('search_method', $methods, $method, '', '', $event.'-search').
1489              fInput('text', 'crit', $crit, 'input-medium', '', '', INPUT_MEDIUM).
1490              eInput($event).
1491              sInput($step).
1492              fInput('submit', 'search', gTxt('go'))
1493          ), '', '', 'get', 'search-form'
1494      );
1495  }
1496  
1497  /**
1498   * Renders a dropdown for selecting Textfilter method preferences.
1499   *
1500   * @param  string $name Element name
1501   * @param  string $val  Current value
1502   * @param  string $id   HTML id attribute for the select input element
1503   * @return string HTML
1504   */
1505  
1506  function pref_text($name, $val, $id = '')
1507  {
1508      $id = ($id) ? $id : $name;
1509      $vals = Txp::get('\Textpattern\Textfilter\Registry')->getMap();
1510  
1511      return selectInput($name, $vals, $val, '', '', $id);
1512  }
1513  
1514  /**
1515   * Attaches a HTML fragment to a DOM node.
1516   *
1517   * @param  string $id        Target DOM node's id
1518   * @param  string $content   HTML fragment
1519   * @param  string $noscript  Noscript alternative
1520   * @param  string $wraptag   Wrapping HTML element
1521   * @param  string $wraptagid Wrapping element's HTML id
1522   * @return string HTML/JS
1523   */
1524  
1525  function dom_attach($id, $content, $noscript = '', $wraptag = 'div', $wraptagid = '')
1526  {
1527      $id = escape_js($id);
1528      $content = escape_js($content);
1529      $wraptag = escape_js($wraptag);
1530      $wraptagid = escape_js($wraptagid);
1531  
1532      $js = <<<EOF
1533          $(function ()
1534          {
1535              $('#{$id}').append($('<{$wraptag} />').attr('id', '{$wraptagid}').html('{$content}'));
1536          });
1537  EOF;
1538  
1539      return script_js($js, (string) $noscript);
1540  }
1541  
1542  /**
1543   * Renders a &lt:script&gt; element.
1544   *
1545   * The $route parameter allows script_js() to be included in fixed page
1546   * locations (e.g. prior to the &lt;/body&gt; tag) but to only render
1547   * its content if the event / step match.
1548   *
1549   * @param  string     $js    JavaScript code
1550   * @param  int|string $flags Flags TEXTPATTERN_SCRIPT_URL | TEXTPATTERN_SCRIPT_ATTACH_VERSION, or boolean or noscript alternative if a string
1551   * @param  array      $route Optional events/steps upon which to add the script
1552   * @return string HTML with embedded script element
1553   * @example
1554   * echo script_js('/js/script.js', TEXTPATTERN_SCRIPT_URL);
1555   */
1556  
1557  function script_js($js, $flags = '', $route = array())
1558  {
1559      static $store = '';
1560      global $event, $step;
1561  
1562      $targetEvent = empty($route[0]) ? null : (is_array($route[0]) ? $route[0] : do_list_unique($route[0]));
1563      $targetStep = empty($route[1]) ? null : (is_array($route[1]) ? $route[1] : do_list_unique($route[1]));
1564  
1565      if (($targetEvent === null || in_array($event, $targetEvent)) && ($targetStep === null || in_array($step, $targetStep))) {
1566          if (is_int($flags)) {
1567              if ($flags & TEXTPATTERN_SCRIPT_URL) {
1568                  if ($flags & TEXTPATTERN_SCRIPT_ATTACH_VERSION && strpos(txp_version, '-dev') === false) {
1569                      $ext = pathinfo($js, PATHINFO_EXTENSION);
1570  
1571                      if ($ext) {
1572                          $js = substr($js, 0, (strlen($ext) + 1) * -1);
1573                          $ext = '.'.$ext;
1574                      }
1575  
1576                      $js .= '.v'.txp_version.$ext;
1577                  }
1578  
1579                  return n.tag(null, 'script', array('src' => $js));
1580              }
1581          }
1582  
1583          $js = preg_replace('#<(/?)(script)#i', '\\x3c$1$2', $js);
1584  
1585          if (is_bool($flags)) {
1586              if (!$flags) {
1587                  $store .= n.$js;
1588  
1589                  return;
1590              } else {
1591                  $js = $store.n.$js;
1592                  $store = '';
1593              }
1594          }
1595  
1596          $js = trim($js);
1597          $out = $js ? n.tag(n.$js.n, 'script') : '';
1598  
1599          if ($flags && $flags !== true) {
1600              $out .= n.tag(n.trim($flags).n, 'noscript');
1601          }
1602  
1603          return $out;
1604      }
1605  
1606      return '';
1607  }
1608  
1609  /**
1610   * Renders a checkbox to set/unset a browser cookie.
1611   *
1612   * @param  string $classname Label text. The cookie's name will be derived from this value
1613   * @param  bool   $form      Create as a stand-along &lt;form&gt; element
1614   * @return string HTML
1615   */
1616  
1617  function cookie_box($classname, $form = true)
1618  {
1619      $name = 'cb_'.$classname;
1620      $id = escape_js($name);
1621      $class = escape_js($classname);
1622  
1623      if (cs('toggle_'.$classname)) {
1624          $value = 1;
1625      } else {
1626          $value = 0;
1627      }
1628  
1629      $newvalue = 1 - $value;
1630  
1631      $out = checkbox($name, 1, (bool) $value, 0, $name).
1632          n.tag(gTxt($classname), 'label', array('for' => $name));
1633  
1634      $js = <<<EOF
1635          $(function ()
1636          {
1637              $('input')
1638                  .filter(function () {
1639                      if ($(this).attr('id') === '{$id}') {
1640                          return true;
1641                      }
1642                  })
1643                  .change(function () {
1644                      setClassRemember('{$class}', $newvalue);
1645                      $(this).parents('form').submit();
1646                  });
1647          });
1648  EOF;
1649  
1650      $out .= script_js($js);
1651  
1652      if ($form) {
1653          if (serverSet('QUERY_STRING')) {
1654              $action = 'index.php?'.serverSet('QUERY_STRING');
1655          } else {
1656              $action = 'index.php';
1657          }
1658  
1659          $out .= eInput(gps('event')).tInput();
1660  
1661          return tag($out, 'form', array(
1662              'class'  => $name,
1663              'method' => 'post',
1664              'action' => $action,
1665          ));
1666      }
1667  
1668      return $out;
1669  }
1670  
1671  /**
1672   * Renders a &lt;fieldset&gt; element.
1673   *
1674   * @param  string $content Enclosed content
1675   * @param  string $legend  Legend text
1676   * @param  string $id      HTML id attribute
1677   * @return string HTML
1678   */
1679  
1680  function fieldset($content, $legend = '', $id = '')
1681  {
1682      return tag(trim(tag($legend, 'legend').n.$content), 'fieldset', array('id' => $id));
1683  }
1684  
1685  /**
1686   * Renders a link element to hook up txpAsyncHref() with request parameters.
1687   *
1688   * See this function's JavaScript companion, txpAsyncHref(), in textpattern.js.
1689   *
1690   * @param  string       $item  Link text
1691   * @param  array        $parms Request parameters; array keys are 'event', 'step', 'thing', 'property'
1692   * @param  string|array $atts  HTML attributes
1693   * @return string HTML
1694   * @since  4.5.0
1695   * @example
1696   * echo asyncHref('Disable', array(
1697   *     'event'    => 'myEvent',
1698   *     'step'     => 'myStep',
1699   *     'thing'    => 'status',
1700   *     'property' => 'disable',
1701   * ));
1702   */
1703  
1704  function asyncHref($item, $parms, $atts = '')
1705  {
1706      global $event, $step;
1707  
1708      $parms = lAtts(array(
1709          'event'    => $event,
1710          'step'     => $step,
1711          'thing'    => '',
1712          'property' => '',
1713      ), $parms);
1714  
1715      $class = $parms['step'].' async';
1716  
1717      if (is_array($atts)) {
1718          $atts['class'] = $class;
1719      } else {
1720          $atts .= ' class="'.txpspecialchars($class).'"';
1721      }
1722  
1723      return href($item, join_qs($parms), $atts);
1724  }
1725  
1726  /**
1727   * Renders an array of items as a HTML list.
1728   *
1729   * This function is used for tag handler functions. Creates a HTML list markup
1730   * from an array of items.
1731   *
1732   * @param   array  $list
1733   * @param   string $wraptag    The HTML element
1734   * @param   string $break      The HTML break element
1735   * @param   string $class      Class applied to the wraptag
1736   * @param   string $breakclass Class applied to break tag
1737   * @param   string $atts       HTML attributes applied to the wraptag
1738   * @param   string $breakatts  HTML attributes applied to the break tag
1739   * @param   string $id         HTML id applied to the wraptag
1740   * @return  string HTML
1741   * @package HTML
1742   * @example
1743   * echo doWrap(array('item1', 'item2'), 'div', 'p');
1744   */
1745  
1746  function doWrap($list, $wraptag, $break, $class = null, $breakclass = null, $atts = null, $breakatts = null, $html_id = null)
1747  {
1748      global $txp_atts;
1749      static $import = array('breakby', 'breakclass', 'wrapform');
1750  
1751      $list = is_array($list) ? array_filter($list, function ($v) {
1752          return $v !== false;
1753      }) : null;
1754  
1755      if (!$list) {
1756          return '';
1757      }
1758  
1759      if (is_array($break)) {
1760          extract($break + array('break' => ''));
1761      }
1762  
1763      foreach ($import as $global) {
1764          if (!isset($$global) && isset($txp_atts[$global])) {
1765              $$global = $txp_atts[$global];
1766          }
1767      }
1768  
1769      if ($html_id) {
1770          $atts .= ' id="'.txpspecialchars($html_id).'"';
1771      }
1772  
1773      if ($class) {
1774          $atts .= ' class="'.txpspecialchars($class).'"';
1775      }
1776  
1777      if ($breakclass) {
1778          $breakatts .= ' class="'.txpspecialchars($breakclass).'"';
1779      }
1780  
1781      if ($break && !empty($breakby)) { // array_merge to reindex
1782          $breakby = array_merge(array_filter(array_map('intval', do_list($breakby))));
1783  
1784          switch ($count = count($breakby)) {
1785              case 0:
1786                  break;
1787              case 1:
1788                  if ($breakby[0] > 0) {
1789                      $breakby[0] == 1 or $newlist = array_chunk($list, $breakby[0]);
1790                      break;
1791                  }
1792                  // no break
1793              default:
1794                  $newlist = array();
1795  
1796                  for ($i = 0; count($list); $i = ($i + 1)%$count) {
1797                      $newlist[] = $breakby[$i] > 0 ? array_splice($list, 0, $breakby[$i]) :  array_splice($list, $breakby[$i]);
1798                  }
1799  
1800          }
1801  
1802          empty($newlist) or $list = array_map('implode', $newlist);
1803      }
1804  
1805      if (isset($txp_atts['trim']) && $txp_atts['trim'] === true) {
1806          $list = array_map('trim', $list);
1807      }
1808  
1809      if ($break === true) {
1810          switch (strtolower($wraptag)) {
1811              case 'ul':
1812              case 'ol':
1813                  $break = 'li';
1814              break;
1815              case 'p':
1816                  $break = 'br';
1817              break;
1818              case 'table':
1819              case 'tbody':
1820              case 'thead':
1821              case 'tfoot':
1822                  $break = 'tr';
1823              break;
1824              case 'tr':
1825                  $break = 'td';
1826              break;
1827              default:
1828                  $break = n;
1829          }
1830      }
1831  
1832      if (strpos($break, '<+>') !== false) {
1833          $content = array_reduce($list, function ($carry, $item) use ($break) {
1834              return $carry.str_replace('<+>', $item, $break);
1835          });
1836      }
1837      // Non-enclosing breaks.
1838      elseif ($break === 'br' || $break === 'hr' || !preg_match('/^\w+$/', $break)) {
1839          if ($break === 'br' || $break === 'hr') {
1840              $break = "<$break $breakatts/>".n;
1841          }
1842  
1843          $content = join($break, $list);
1844      } else {
1845          $content = "<{$break}{$breakatts}>".join("</$break>".n."<{$break}{$breakatts}>", $list)."</{$break}>";
1846      }
1847  
1848      if (!empty($wrapform)) {
1849          $content = str_replace('<+>', $content, parse_form($wrapform));
1850      }
1851  
1852      if (empty($wraptag)) {
1853          return $content;
1854      } else {
1855          return tag($content, $wraptag, $atts);
1856      }
1857  }
1858  
1859  /**
1860   * Renders anything as a HTML tag.
1861   *
1862   * Used for tag handler functions.
1863   *
1864   * If $content is empty, renders a self-closing tag.
1865   *
1866   * @param   string $content The wrapped item
1867   * @param   string $tag     The HTML tag
1868   * @param   string $class   HTML class
1869   * @param   string $atts    HTML attributes
1870   * @param   string $id      HTML id
1871   * @return  string HTML
1872   * @package HTML
1873   * @example
1874   * echo doTag('', 'meta', '', 'name="description" content="Some content"');
1875   */
1876  
1877  function doTag($content, $tag, $class = '', $atts = '', $id = '')
1878  {
1879      if (!$tag) {
1880          return $content;
1881      }
1882  
1883      if ($id) {
1884          $atts .= ' id="'.txpspecialchars($id).'"';
1885      }
1886  
1887      if ($class) {
1888          $atts .= ' class="'.txpspecialchars($class).'"';
1889      }
1890  
1891      return (string)$content !== '' ? tag($content, $tag, $atts) : "<$tag $atts />";
1892  }
1893  
1894  /**
1895   * Renders a label.
1896   *
1897   * This function is mostly used for rendering headings in tag handler functions.
1898   *
1899   * If no $labeltag is given, label is separated from the content with
1900   * a &lt;br&gt;.
1901   *
1902   * @param   string $label    The label
1903   * @param   string $labeltag The HTML element
1904   * @return  string HTML
1905   * @package HTML
1906   * @example
1907   * echo doLabel('My label', 'h3');
1908   */
1909  
1910  function doLabel($label = '', $labeltag = '')
1911  {
1912      if ($label) {
1913          return (empty($labeltag) ? $label.'<br />' : tag($label, $labeltag));
1914      }
1915  
1916      return '';
1917  }

title

Description

title

Description

title

Description

title

title

Body