Textpattern | PHP Cross Reference | Content Management Systems |
Description: Collection of HTML form 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 form widgets. 26 * 27 * @package Form 28 */ 29 30 /** 31 * Generates a radio button toggle. 32 * 33 * @param array $values The values as an array 34 * @param string $field The field name 35 * @param string $checked The checked button, takes a value from $vals 36 * @param int $tabindex The HTML tabindex 37 * @param string $id The HTML id 38 * @return string A HTML radio button set 39 * @example 40 * echo radioSet(array( 41 * 'value1' => 'Label1', 42 * 'value2' => 'Label2', 43 * ), 'myInput', 'value1'); 44 */ 45 46 function radioSet($values, $field, $checked = '', $tabindex = 0, $id = '') 47 { 48 if ($id) { 49 $id = $id.'-'.$field; 50 } else { 51 $id = $field; 52 } 53 54 $out = array(); 55 56 foreach ((array) $values as $value => $label) { 57 $out[] = radio($field, $value, (string) $value === (string) $checked, $id.'-'.$value, $tabindex); 58 $out[] = n.tag($label, 'label', array('for' => $id.'-'.$value)); 59 } 60 61 return join('', $out); 62 } 63 64 /** 65 * Generates a Yes/No radio button toggle. 66 * 67 * These buttons are booleans. 'Yes' will have a value of 1 and 'No' is 0. 68 * 69 * @param string $field The field name 70 * @param string $checked The checked button, either '1', '0' 71 * @param int $tabindex The HTML tabindex 72 * @param string $id The HTML id 73 * @return string HTML 74 * @see radioSet() 75 * @example 76 * echo form( 77 * 'Is this an example?'. 78 * yesnoRadio('is_example', 1) 79 * ); 80 */ 81 82 function yesnoRadio($field, $checked = '', $tabindex = 0, $id = '') 83 { 84 $vals = array( 85 '0' => gTxt('no'), 86 '1' => gTxt('yes'), 87 ); 88 89 return radioSet($vals, $field, $checked, $tabindex, $id); 90 } 91 92 /** 93 * Generates an On/Off radio button toggle. 94 * 95 * @param string $field The field name 96 * @param string $checked The checked button, either '1', '0' 97 * @param int $tabindex The HTML tabindex 98 * @param string $id The HTML id 99 * @return string HTML 100 * @see radioSet() 101 */ 102 103 function onoffRadio($field, $checked = '', $tabindex = 0, $id = '') 104 { 105 $vals = array( 106 '0' => gTxt('off'), 107 '1' => gTxt('on'), 108 ); 109 110 return radioSet($vals, $field, $checked, $tabindex, $id); 111 } 112 113 /** 114 * Generates a select field. 115 * 116 * @param string $name The field 117 * @param array $array The values as an array array( 'value' => 'label' ) 118 * @param mixed $value The selected option(s). If an array, renders the select multiple 119 * @param bool $blank_first If TRUE, prepends an empty option to the list 120 * @param mixed $onchange If TRUE submits the form when an option is changed. If a string, inserts it to the select tag 121 * @param string $select_id The HTML id 122 * @param bool $check_type Type-agnostic comparison 123 * @param bool $disabled If TRUE renders the select disabled 124 * @return string HTML 125 * @example 126 * echo selectInput('myInput', array( 127 * 'value1' => 'Label1', 128 * 'value2' => 'Label2', 129 * )); 130 */ 131 132 function selectInput($name = '', $array = array(), $value = '', $blank_first = false, $onchange = '', $select_id = '', $check_type = false, $disabled = false) 133 { 134 $out = array(); 135 136 $selected = false; 137 $multiple = is_array($value); 138 $multiple or $value = (string) $value; 139 140 if (is_array($disabled)) { 141 $disable = $disabled; 142 $disabled = false; 143 } else { 144 $disable = array(); 145 } 146 147 if (is_array($blank_first)) { 148 $array = $blank_first + $array; 149 $blank_first = false; 150 } 151 152 foreach ($array as $avalue => $alabel) { 153 $atts = array('value' => $avalue, 'dir' => 'auto', 'disabled' => in_array($avalue, $disable)); 154 155 if (!$multiple && $value === (string) $avalue || $multiple && in_array($avalue, $value)) { 156 $atts['selected'] = $selected = true; 157 } 158 159 if (is_array($alabel)) { 160 $atts = $alabel + $atts; 161 $alabel = isset($atts['title']) ? $atts['title'] : $avalue; 162 unset($atts['title']); 163 } 164 165 $atts = join_atts($atts, TEXTPATTERN_STRIP_NONE); 166 $out[] = '<option'.$atts.'>'.txpspecialchars($alabel).'</option>'; 167 } 168 169 if ($blank_first) { 170 array_unshift($out, '<option value=""'.($selected === false ? ' selected="selected"' : '').'> </option>'); 171 } 172 173 $name_m = (is_array($name) ? $name['name'] : $name).($multiple ? '[]' : ''); 174 175 $atts = join_atts((is_array($name) ? $name : array( 176 'name' => $name.($multiple ? '[]' : '') 177 )) + array( 178 'id' => $select_id, 179 'disabled' => (bool) $disabled, 180 'multiple' => $multiple 181 ), TEXTPATTERN_STRIP_EMPTY); 182 183 if ((string) $onchange === '1') { 184 $atts .= ' data-submit-on="change"'; 185 } elseif ($onchange) { 186 $atts .= ' '.trim($onchange); 187 } 188 189 return n.'<select'.$atts.'>'.n.join(n, $out).n.'</select>'.n 190 .($multiple ? hInput($name_m, '').n : ''); // TODO: use jQuery UI selectmenu? 191 } 192 193 /** 194 * Generates a tree structured select field. 195 * 196 * This field takes a NSTREE structure as an associative array. This is mainly 197 * used for categories. 198 * 199 * @param string $select_name The field 200 * @param array $array The values as an array 201 * @param string $value The selected option. Takes a value from $value 202 * @param string $select_id The HTML id 203 * @param int $truncate Truncate labels to certain length. Disabled if set <4. 204 * @return string HTML 205 * @see getTree() 206 */ 207 208 function treeSelectInput($select_name = '', $array = array(), $value = '', $select_id = '', $truncate = 0, $atts = array()) 209 { 210 $out = array(); 211 212 $doctype = get_pref('doctype'); 213 $selected = false; 214 215 foreach ($array as $a) { 216 if ($a['name'] == 'root') { 217 continue; 218 } 219 220 if ((string) $a['name'] === (string) $value) { 221 $sel = ' selected="selected"'; 222 $selected = true; 223 } else { 224 $sel = ''; 225 } 226 227 $sp = str_repeat(sp.sp, $a['level']); 228 229 if (($truncate > 3) && (strlen(utf8_decode($a['title'])) > $truncate)) { 230 $htmltitle = ' title="'.txpspecialchars($a['title']).'"'; 231 $a['title'] = preg_replace('/^(.{0,'.($truncate - 3).'}).*$/su', '$1', $a['title']); 232 $hellip = '…'; 233 } else { 234 $htmltitle = $hellip = ''; 235 } 236 237 $data_level = ''; 238 if ($doctype !== 'xhtml') { 239 $data_level = ' data-level="'.$a['level'].'"'; 240 } 241 242 $out[] = '<option value="'.txpspecialchars($a['name']).'"'.$htmltitle.$sel.$data_level.' dir="auto">'.$sp.txpspecialchars($a['title']).$hellip.'</option>'; 243 } 244 245 array_unshift($out, '<option value=""'.($selected === false ? ' selected="selected"' : '').'> </option>'); 246 247 return n.tag(n.join(n, $out).n, 'select', array( 248 'id' => $select_id, 249 'name' => $select_name, 250 ) + $atts); 251 } 252 253 /** 254 * Render HTML <select> element for choosing a timezone. 255 * 256 * @param string $name Element name 257 * @param string $value Selected timezone 258 * @param bool $blank_first Add empty first option 259 * @param bool|string $onchange 260 * @param string $select_id HTML id attribute 261 * @return string HTML markup 262 * @since 4.7.0 263 * @todo Might be a better way of doing this, perhaps introducing optgroup to selectInput() 264 */ 265 266 function timezoneSelectInput($name = '', $value = '', $blank_first = '', $onchange = '', $select_id = '') 267 { 268 if ($details = Txp::get('\Textpattern\Date\Timezone')->getTimeZones()) { 269 $thiscontinent = ''; 270 $selected = false; 271 272 foreach ($details as $timezone_id => $tz) { 273 extract($tz); 274 275 if ($value == $timezone_id) { 276 $selected = true; 277 } 278 279 if ($continent !== $thiscontinent) { 280 if ($thiscontinent !== '') { 281 $out[] = n.'</optgroup>'; 282 } 283 284 $out[] = n.'<optgroup label="'.gTxt($continent).'">'; 285 $thiscontinent = $continent; 286 } 287 288 $where = gTxt(str_replace('_', ' ', $city)) 289 .(!empty($subcity) ? '/'.gTxt(str_replace('_', ' ', $subcity)) : '').t 290 /*."($abbr)"*/; 291 292 $out[] = n.'<option value="'.txpspecialchars($timezone_id).'"'.($value == $timezone_id ? ' selected="selected"' : '').' dir="auto">'.$where.'</option>'; 293 } 294 295 $out[] = n.'</optgroup>'; 296 297 return n.'<select'.($select_id ? ' id="'.$select_id.'"' : '').' name="'.$name.'"'. 298 ($onchange == 1 ? ' onchange="submit(this.form);"' : $onchange). 299 '>'. 300 ($blank_first ? n.'<option value=""'.($selected == false ? ' selected="selected"' : '').'> </option>' : ''). 301 join('', $out). 302 n.'</select>'; 303 } 304 305 return ''; 306 } 307 308 /** 309 * Generic form input. 310 * 311 * @param string $type The input type 312 * @param string $name The input name 313 * @param string $value The value 314 * @param string $class The HTML class 315 * @param string $title The tooltip 316 * @param string $onClick Inline JavaScript attached to the click event 317 * @param int $size The input size 318 * @param int $tab The HTML tabindex 319 * @param string $id The HTML id 320 * @param bool $disabled If TRUE renders the input disabled 321 * @param bool $required If TRUE the field is marked as required 322 * @param string $placeholder The placeholder value displayed when the field is empty 323 * @return string HTML input 324 * @example 325 * echo fInput('text', 'myInput', 'My example value'); 326 */ 327 328 function fInput($type, $name, $value, $class = '', $title = '', $onClick = '', $size = 0, $tab = 0, $id = '', $disabled = false, $required = false, $placeholder = null) 329 { 330 $atts = (is_array($name) ? $name : array('name' => $name)) + array( 331 'class' => $class, 332 'id' => $id, 333 'type' => $type, 334 'size' => (int) $size, 335 'title' => $title, 336 'onclick' => $onClick, 337 'tabindex' => (int) $tab, 338 'disabled' => (bool) $disabled, 339 'required' => (bool) $required, 340 'placeholder' => $placeholder, 341 ); 342 343 if ($atts['required'] && !isset($atts['placeholder']) 344 && in_array($atts['type'], array('email', 'password', 'search', 'tel', 'text', 'url')) 345 ) { 346 $atts['placeholder'] = gTxt('required'); 347 } 348 349 $atts = join_atts($atts, TEXTPATTERN_STRIP_EMPTY); 350 351 if ($type != 'file' && $type != 'image') { 352 $atts .= join_atts(array('value' => (string) $value), TEXTPATTERN_STRIP_NONE); 353 } 354 355 return n.tag_void('input', $atts); 356 } 357 358 /** 359 * Hidden form input. 360 * 361 * @param string/array $name The name 362 * @param string $value The value 363 * @return string HTML input 364 * @example 365 * echo hInput('myInput', 'hidden value'); 366 */ 367 368 function hInput($name, $value = null, $glue = ',') 369 { 370 if (!is_array($name)) { 371 return fInput('hidden', $name, $value); 372 } 373 374 return array_walk($name, function (&$v, $n, $glue) { 375 $v = fInput('hidden', $n, is_array($v) ? implode($glue, $v) : $v); 376 }, $glue) ? implode($name) : false; 377 } 378 379 /** 380 * Hidden step input. 381 * 382 * @param string $step The step 383 * @return string HTML input 384 * @see form() 385 * @see eInput() 386 * @example 387 * echo form( 388 * eInput('event'). 389 * sInput('step') 390 * ); 391 */ 392 393 function sInput($step) 394 { 395 return hInput('step', $step); 396 } 397 398 /** 399 * Hidden event input. 400 * 401 * @param string $event The event 402 * @return string HTML input 403 * @see form() 404 * @see sInput() 405 * @example 406 * echo form( 407 * eInput('event'). 408 * sInput('step') 409 * ); 410 */ 411 412 function eInput($event) 413 { 414 return hInput('event', $event); 415 } 416 417 /** 418 * Hidden form token input. 419 * 420 * @return string A hidden HTML input containing a CSRF token 421 * @see bouncer() 422 * @see form_token() 423 */ 424 425 function tInput() 426 { 427 return hInput('_txp_token', form_token()); 428 } 429 430 /** 431 * A checkbox. 432 * 433 * @param string $name The field 434 * @param string $value The value 435 * @param bool $checked If TRUE the box is checked 436 * @param int $tabindex The HTML tabindex 437 * @param string $id The HTML id 438 * @return string HTML input 439 * @example 440 * echo checkbox('name', 'value', true); 441 */ 442 443 function checkbox($name, $value, $checked = true, $tabindex = 0, $id = '') 444 { 445 $class = 'checkbox'; 446 447 if ($checked) { 448 $class .= ' active'; 449 } 450 451 $atts = join_atts(array( 452 'class' => $class, 453 'id' => $id, 454 'name' => $name, 455 'type' => 'checkbox', 456 'checked' => (bool) $checked, 457 'tabindex' => (int) $tabindex, 458 ), TEXTPATTERN_STRIP_EMPTY); 459 460 $atts .= join_atts(array('value' => (string) $value), TEXTPATTERN_STRIP_NONE); 461 462 return n.tag_void('input', $atts); 463 } 464 465 /** 466 * A checkbox without an option to set the value. 467 * 468 * @param string $name The field 469 * @param bool $value If TRUE the box is checked 470 * @param int $tabindex The HTML tabindex 471 * @param string $id The HTML id 472 * @return string HTML input 473 * @access private 474 * @see checkbox() 475 */ 476 477 function checkbox2($name, $value, $tabindex = 0, $id = '') 478 { 479 return checkbox($name, 1, $value, $tabindex, $id); 480 } 481 482 /** 483 * A single radio button. 484 * 485 * @param string $name The field 486 * @param string $value The value 487 * @param bool $checked If TRUE, the button is selected 488 * @param string $id The HTML id 489 * @param int $tabindex The HTML tabindex 490 * @return string HTML input 491 */ 492 493 function radio($name, $value, $checked = true, $id = '', $tabindex = 0) 494 { 495 $class = 'radio'; 496 497 if ($checked) { 498 $class .= ' active'; 499 } 500 501 $atts = join_atts(array( 502 'class' => $class, 503 'id' => $id, 504 'name' => $name, 505 'type' => 'radio', 506 'checked' => (bool) $checked, 507 'tabindex' => (int) $tabindex, 508 ), TEXTPATTERN_STRIP_EMPTY); 509 510 $atts .= join_atts(array('value' => (string) $value), TEXTPATTERN_STRIP_NONE); 511 512 return n.tag_void('input', $atts); 513 } 514 515 /** 516 * Generates a form element. 517 * 518 * This form will contain a CSRF token if called on an authenticated page. 519 * 520 * @param string $contents The form contents 521 * @param string $style Inline styles added to the form 522 * @param string $onsubmit JavaScript run when the form is sent 523 * @param string $method The form method, e.g. "post", "get" 524 * @param string $class The HTML class 525 * @param string $fragment A URL fragment added to the form target 526 * @param string $id The HTML id 527 * @param string $role ARIA role name 528 * @param bool $allow_autocomplete If FALSE, the form is set to autocomplete="off" 529 * @return string HTML form element 530 */ 531 532 function form($contents, $style = '', $onsubmit = '', $method = 'post', $class = '', $fragment = '', $id = '', $role = '', $allow_autocomplete = true) 533 { 534 $action = 'index.php'; 535 $autocomplete = ''; 536 537 if ($onsubmit) { 538 $onsubmit = 'return '.$onsubmit; 539 } 540 541 if ($fragment) { 542 $action .= '#'.$fragment; 543 } 544 545 if ($allow_autocomplete === false) { 546 $autocomplete = 'off'; 547 } 548 549 return n.tag($contents.tInput().n, 'form', array( 550 'class' => $class, 551 'id' => $id, 552 'method' => $method, 553 'action' => $action, 554 'onsubmit' => $onsubmit, 555 'role' => $role, 556 'autocomplete' => $autocomplete, 557 'style' => $style, 558 )); 559 } 560 561 /** 562 * Gets and sanitises a field from a prefixed core database table. 563 * 564 * @param string $name The field 565 * @param string $event The table 566 * @param string $identifier The field used for selecting 567 * @param string $id The value used for selecting 568 * @return string HTML 569 * @access private 570 * @see fetch() 571 * @see txpspecialchars() 572 */ 573 574 function fetch_editable($name, $event, $identifier, $id) 575 { 576 $q = fetch($name, 'txp_'.$event, $identifier, $id); 577 578 return txpspecialchars($q); 579 } 580 581 /** 582 * A textarea. 583 * 584 * @param string $name The field 585 * @param int $h The field height in pixels 586 * @param int $w The field width in pixels 587 * @param string $thing The value 588 * @param string $id The HTML id 589 * @param int $rows Rows 590 * @param int $cols Columns 591 * @param string $placeholder The placeholder value displayed when the field is empty 592 * @param bool $required If TRUE the field is marked as required 593 * @return string HTML 594 */ 595 596 function text_area($name, $h = 0, $w = 0, $thing = '', $id = '', $rows = 5, $cols = 40, $placeholder = null, $required = false) 597 { 598 $style = ''; 599 600 if ($w) { 601 $style .= 'width:'.intval($w).'px;'; 602 } 603 604 if ($h) { 605 $style .= 'height:'.intval($h).'px;'; 606 } 607 608 if ((string) $thing === '') { 609 $thing = null; 610 } else { 611 $thing = txpspecialchars($thing); 612 } 613 614 if (!intval($rows)) { 615 $rows = 5; 616 } 617 618 if (!intval($cols)) { 619 $cols = 40; 620 } 621 622 $atts = (is_array($name) ? $name : array('name' => $name)) + array( 623 'id' => $id, 624 'rows' => (int) $rows, 625 'cols' => (int) $cols, 626 'style' => $style, 627 'required' => (bool) $required, 628 'placeholder' => $placeholder, 629 ); 630 631 if (!isset($atts['placeholder'])) { 632 $atts['placeholder'] = $atts['required'] ? gTxt('required') : false; 633 } 634 635 return n.tag($thing, 'textarea', $atts); 636 } 637 638 /** 639 * Generates a select field with a name "type". 640 * 641 * @param array $options 642 * @return string 643 * @access private 644 * @see selectInput() 645 */ 646 647 function type_select($options) 648 { 649 return n.'<select name="type">'.type_options($options).'</select>'; 650 } 651 652 /** 653 * Generates a list of options for use in a select field. 654 * 655 * @param array $array 656 * @return string 657 * @access private 658 * @see selectInput() 659 */ 660 661 function type_options($array) 662 { 663 foreach ($array as $a => $b) { 664 $out[] = n.'<option value="'.$a.'">'.gTxt($b).'</option>'; 665 } 666 667 return join('', $out); 668 } 669 670 /** 671 * Generates a list of radio buttons wrapped in an unordered list. 672 * 673 * @param string $name The field 674 * @param array $values The values as an array array( $value => $label ) 675 * @param string $current_val The selected option. Takes a value from $value 676 * @param string $hilight_val The highlighted list item 677 * @param string|array $atts HTML attributes 678 * @return string HTML 679 */ 680 681 function radio_list($name, $values, $current_val = '', $hilight_val = '', $atts = array('class' => 'plain-list')) 682 { 683 foreach ($values as $value => $label) { 684 $id = $name.'-'.$value; 685 $class = 'status-'.$value; 686 687 if ((string) $value === (string) $hilight_val) { 688 $label = strong($label); 689 $class .= ' active'; 690 } 691 692 $out[] = tag( 693 radio($name, $value, ((string) $current_val === (string) $value), $id). 694 n.tag($label, 'label', array('for' => $id)), 695 'li', 696 array('class' => $class) 697 ); 698 } 699 700 return tag(n.join(n, $out).n, 'ul', $atts); 701 } 702 703 /** 704 * Generates a field used to store and set a date. 705 * 706 * @param string $name The field 707 * @param string $datevar The strftime format the date is displayed 708 * @param int $time The displayed date as a UNIX timestamp 709 * @param int $tab The HTML tabindex 710 * @param string $id The HTML id 711 * @return string HTML 712 * @access private 713 * @example 714 * echo tsi('year', '%Y', 1200000000); 715 */ 716 717 function tsi($name, $datevar, $time, $tab = 0, $id = '') 718 { 719 static $placeholders = array( 720 '%Y' => 'yyyy', 721 '%m' => 'mm', 722 '%d' => 'dd', 723 '%H' => 'hh', 724 '%M' => 'mn', 725 '%S' => 'ss', 726 ); 727 728 $value = $placeholder = ''; 729 $size = INPUT_TINY; 730 $pattern = '([0-5][0-9])'; 731 732 if ((int) $time) { 733 $value = safe_strftime($datevar, (int) $time); 734 } 735 736 if (isset($placeholders[$datevar])) { 737 $placeholder = gTxt($placeholders[$datevar]); 738 } 739 740 if ($datevar == '%Y' || $name == 'year' || $name == 'exp_year') { 741 $class = 'input-year'; 742 $pattern = '[0-9]{4}'; 743 $size = INPUT_XSMALL; 744 $title = 'input_year'; 745 } 746 747 if ($datevar == '%m' || $name == 'month' || $name == 'exp_month') { 748 $class = 'input-month'; 749 $pattern = '(0[1-9]|1[012])'; 750 $title = 'input_month'; 751 } 752 753 if ($datevar == '%d' || $name == 'day' || $name == 'exp_day') { 754 $class = 'input-day'; 755 $pattern = '(0[1-9]|[12][0-9]|3[01])'; 756 $title = 'input_day'; 757 } 758 759 if ($datevar == '%H' || $name == 'hour' || $name == 'exp_hour') { 760 $class = 'input-hour'; 761 $pattern = '([0-1][0-9]|2[0-3])'; 762 $title = 'input_hour'; 763 } 764 765 if ($datevar == '%M' || $name == 'minute' || $name == 'exp_minute') { 766 $class = 'input-minute'; 767 $pattern = '([0-5][0-9])'; 768 $title = 'input_minute'; 769 } 770 771 if ($datevar == '%S' || $name == 'second' || $name == 'exp_second') { 772 $class = 'input-second'; 773 $pattern = '([0-5][0-9])'; 774 $title = 'input_second'; 775 } 776 777 return n.tag_void('input', array( 778 'class' => $class, 779 'id' => $id, 780 'name' => $name, 781 'type' => 'text', 782 'inputmode' => 'numeric', 783 'pattern' => $pattern, 784 'size' => (int) $size, 785 'maxlength' => $size, 786 'title' => gTxt($title), 787 'aria-label' => gTxt($title), 788 'placeholder' => $placeholder, 789 'tabindex' => (int) $tab, 790 'value' => $value, 791 )); 792 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
title