1<?php 2/* 3 * vim:set softtabstop=4 shiftwidth=4 expandtab: 4 * 5 * LICENSE: GNU Affero General Public License, version 3 (AGPL-3.0-or-later) 6 * Copyright 2001 - 2020 Ampache.org 7 * 8 * This program is free software: you can redistribute it and/or modify 9 * it under the terms of the GNU Affero General Public License as published by 10 * the Free Software Foundation, either version 3 of the License, or 11 * (at your option) any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU Affero General Public License for more details. 17 * 18 * You should have received a copy of the GNU Affero General Public License 19 * along with this program. If not, see <https://www.gnu.org/licenses/>. 20 * 21 */ 22 23declare(strict_types=0); 24 25namespace Ampache\Module\Util; 26 27use Ampache\Config\ConfigContainerInterface; 28use Ampache\Module\Playback\Localplay\LocalPlay; 29use Ampache\Module\Playback\Localplay\LocalPlayTypeEnum; 30use Ampache\Repository\Model\Metadata\Repository\MetadataField; 31use Ampache\Repository\Model\Playlist; 32use Ampache\Repository\Model\Plugin; 33use Ampache\Config\AmpConfig; 34use Ampache\Module\System\Core; 35use Ampache\Module\System\Dba; 36use Ampache\Repository\Model\Preference; 37 38/** 39 * A collection of methods related to the user interface 40 */ 41class Ui implements UiInterface 42{ 43 private static $_classes; 44 private static $_ticker; 45 private static $_icon_cache; 46 private static $_image_cache; 47 48 private ConfigContainerInterface $configContainer; 49 50 public function __construct( 51 ConfigContainerInterface $configContainer 52 ) { 53 $this->configContainer = $configContainer; 54 } 55 56 /** 57 * find_template 58 * 59 * Return the path to the template file wanted. The file can be overwritten 60 * by the theme if it's not a php file, or if it is and if option 61 * allow_php_themes is set to true. 62 * @param string $template 63 * @return string 64 */ 65 public static function find_template($template, bool $extern = false) 66 { 67 $path = AmpConfig::get('theme_path') . '/templates/' . $template; 68 $realpath = __DIR__ . '/../../../public/' . $path; 69 $extension = strtolower(pathinfo($path, PATHINFO_EXTENSION)); 70 if (($extension != 'php' || AmpConfig::get('allow_php_themes')) && file_exists($realpath) && is_file($realpath)) { 71 return $path; 72 } else { 73 if ($extern === true) { 74 return '/templates/' . $template; 75 } 76 77 return __DIR__ . '/../../../public/templates/' . $template; 78 } 79 } 80 81 public function accessDenied(string $error = 'Access Denied'): void 82 { 83 // Clear any buffered crap 84 ob_end_clean(); 85 header("HTTP/1.1 403 $error"); 86 require_once self::find_template('show_denied.inc.php'); 87 } 88 89 /** 90 * ajax_include 91 * 92 * Does some trickery with the output buffer to return the output of a 93 * template. 94 * @param string $template 95 * @return string 96 */ 97 public static function ajax_include($template) 98 { 99 ob_start(); 100 require self::find_template('') . $template; 101 $output = ob_get_contents(); 102 ob_end_clean(); 103 104 return $output; 105 } 106 107 /** 108 * check_iconv 109 * 110 * Checks to see whether iconv is available; 111 * @return boolean 112 */ 113 public static function check_iconv() 114 { 115 if (function_exists('iconv') && function_exists('iconv_substr')) { 116 return true; 117 } 118 119 return false; 120 } 121 122 /** 123 * check_ticker 124 * 125 * Stupid little cutesie thing to ratelimit output of long-running 126 * operations. 127 * @return boolean 128 */ 129 public static function check_ticker() 130 { 131 if (!isset(self::$_ticker) || (time() > self::$_ticker + 1)) { 132 self::$_ticker = time(); 133 134 return true; 135 } 136 137 return false; 138 } 139 140 /** 141 * clean_utf8 142 * 143 * Removes characters that aren't valid in XML (which is a subset of valid 144 * UTF-8, but close enough for our purposes.) 145 * See http://www.w3.org/TR/2006/REC-xml-20060816/#charsets 146 * @param string $string 147 * @return string 148 */ 149 public static function clean_utf8($string) 150 { 151 if ($string) { 152 $clean = preg_replace( 153 '/[^\x{9}\x{a}\x{d}\x{20}-\x{d7ff}\x{e000}-\x{fffd}\x{10000}-\x{10ffff}]|[\x{7f}-\x{84}\x{86}-\x{9f}\x{fdd0}-\x{fddf}\x{1fffe}-\x{1ffff}\x{2fffe}-\x{2ffff}\x{3fffe}-\x{3ffff}\x{4fffe}-\x{4ffff}\x{5fffe}-\x{5ffff}\x{6fffe}-\x{6ffff}\x{7fffe}-\x{7ffff}\x{8fffe}-\x{8ffff}\x{9fffe}-\x{9ffff}\x{afffe}-\x{affff}\x{bfffe}-\x{bffff}\x{cfffe}-\x{cffff}\x{dfffe}-\x{dffff}\x{efffe}-\x{effff}\x{ffffe}-\x{fffff}\x{10fffe}-\x{10ffff}]/u', 154 '', 155 $string 156 ); 157 158 if ($clean) { 159 return rtrim((string)$clean); 160 } 161 162 debug_event(self::class, 'Charset cleanup failed, something might break', 1); 163 } 164 165 return ''; 166 } 167 168 /** 169 * format_bytes 170 * 171 * Turns a size in bytes into the best human-readable value 172 * @param $value 173 * @param integer $precision 174 * @return string 175 */ 176 public static function format_bytes($value, $precision = 2) 177 { 178 $pass = 0; 179 while (strlen((string)floor($value)) > 3) { 180 $value /= 1024; 181 $pass++; 182 } 183 184 switch ($pass) { 185 case 1: 186 $unit = 'kB'; 187 break; 188 case 2: 189 $unit = 'MB'; 190 break; 191 case 3: 192 $unit = 'GB'; 193 break; 194 case 4: 195 $unit = 'TB'; 196 break; 197 case 5: 198 $unit = 'PB'; 199 break; 200 default: 201 $unit = 'B'; 202 break; 203 } 204 205 return ((string)round($value, $precision)) . ' ' . $unit; 206 } 207 208 /** 209 * unformat_bytes 210 * 211 * Parses a human-readable size 212 * @param $value 213 * @return string 214 * @noinspection PhpMissingBreakStatementInspection 215 */ 216 public static function unformat_bytes($value) 217 { 218 if (preg_match('/^([0-9]+) *([[:alpha:]]+)$/', (string)$value, $matches)) { 219 $value = $matches[1]; 220 $unit = strtolower(substr($matches[2], 0, 1)); 221 } else { 222 return (string)$value; 223 } 224 225 switch ($unit) { 226 case 'p': 227 $value *= 1024; 228 // Intentional break fall-through 229 case 't': 230 $value *= 1024; 231 // Intentional break fall-through 232 case 'g': 233 $value *= 1024; 234 // Intentional break fall-through 235 case 'm': 236 $value *= 1024; 237 // Intentional break fall-through 238 case 'k': 239 $value *= 1024; 240 // Intentional break fall-through 241 } 242 243 return (string)$value; 244 } 245 246 /** 247 * get_icon 248 * 249 * Returns an <img> or <svg> tag for the specified icon 250 * @param string $name 251 * @param string $title 252 * @param string $id_attrib 253 * @param string $class_attrib 254 * @return string 255 */ 256 public static function get_icon($name, $title = null, $id_attrib = null, $class_attrib = null) 257 { 258 if (is_array($name)) { 259 $hover_name = $name[1]; 260 $name = $name[0]; 261 } 262 263 $title = $title ?: T_(ucfirst($name)); 264 $icon_url = self::_find_icon($name); 265 $icontype = pathinfo($icon_url, 4); 266 if (isset($hover_name)) { 267 $hover_url = self::_find_icon($hover_name); 268 } 269 if ($icontype == 'svg') { 270 // load svg file 271 $svgicon = simplexml_load_file($icon_url); 272 273 if (empty($svgicon->title)) { 274 $svgicon->addChild('title', $title); 275 } else { 276 $svgicon->title = $title; 277 } 278 if (empty($svgicon->desc)) { 279 $svgicon->addChild('desc', $title); 280 } else { 281 $svgicon->desc = $title; 282 } 283 284 if (!empty($id_attrib)) { 285 $svgicon->addAttribute('id', $id_attrib); 286 } 287 288 $class_attrib = ($class_attrib) ?: 'icon icon-' . $name; 289 $svgicon->addAttribute('class', $class_attrib); 290 291 $tag = explode("\n", $svgicon->asXML(), 2)[1]; 292 } else { 293 // fall back to png 294 $tag = '<img src="' . $icon_url . '" '; 295 $tag .= 'alt="' . $title . '" '; 296 $tag .= 'title="' . $title . '" '; 297 if ($id_attrib !== null) { 298 $tag .= 'id="' . $id_attrib . '" '; 299 } 300 if ($class_attrib !== null) { 301 $tag .= 'class="' . $class_attrib . '" '; 302 } 303 if (isset($hover_name) && isset($hover_url)) { 304 $tag .= 'onmouseover="this.src=\'' . $hover_url . '\'; return true;"'; 305 $tag .= 'onmouseout="this.src=\'' . $icon_url . '\'; return true;" '; 306 } 307 $tag .= '/>'; 308 } 309 310 return $tag; 311 } 312 313 /** 314 * _find_icon 315 * 316 * Does the finding icon thing. match svg first over png 317 * @param string $name 318 * @return string 319 */ 320 private static function _find_icon($name) 321 { 322 if (isset(self::$_icon_cache[$name])) { 323 return self::$_icon_cache[$name]; 324 } 325 326 $path = AmpConfig::get('theme_path') . '/images/icons/'; 327 $filesearch = glob(__DIR__ . '/../../../public/' . $path . 'icon_' . $name . '.{svg,png}', GLOB_BRACE); 328 if (empty($filesearch)) { 329 // if the theme is missing an icon. fall back to default images folder 330 $filename = 'icon_' . $name . '.png'; 331 $path = '/images/'; 332 } else { 333 $filename = pathinfo($filesearch[0], 2); 334 } 335 $url = AmpConfig::get('web_path') . $path . $filename; 336 // cache the url so you don't need to keep searching 337 self::$_icon_cache[$name] = $url; 338 339 return $url; 340 } 341 342 /** 343 * get_image 344 * 345 * Returns an <img> or <svg> tag for the specified image 346 * @param string $name 347 * @param string $title 348 * @param string $id_attrib 349 * @param string $class_attrib 350 * @return string 351 */ 352 public static function get_image($name, $title = null, $id_attrib = null, $class_attrib = null) 353 { 354 if (is_array($name)) { 355 $hover_name = $name[1]; 356 $name = $name[0]; 357 } 358 359 $title = $title ?: ucfirst($name); 360 361 $image_url = self::_find_image($name); 362 $imagetype = pathinfo($image_url, 4); 363 if (isset($hover_name)) { 364 $hover_url = self::_find_image($hover_name); 365 } 366 if ($imagetype == 'svg') { 367 // load svg file 368 $svgimage = simplexml_load_file($image_url); 369 370 $svgimage->addAttribute('class', 'image'); 371 372 if (empty($svgimage->title)) { 373 $svgimage->addChild('title', $title); 374 } else { 375 $svgimage->title = $title; 376 } 377 if (empty($svgimage->desc)) { 378 $svgimage->addChild('desc', $title); 379 } else { 380 $svgimage->desc = $title; 381 } 382 383 if (!empty($id_attrib)) { 384 $svgimage->addAttribute('id', $id_attrib); 385 } 386 387 $class_attrib = ($class_attrib) ?: 'image image-' . $name; 388 $svgimage->addAttribute('class', $class_attrib); 389 390 $tag = explode("\n", $svgimage->asXML(), 2)[1]; 391 } else { 392 // fall back to png 393 $tag = '<img src="' . $image_url . '" '; 394 $tag .= 'alt="' . $title . '" '; 395 $tag .= 'title="' . $title . '" '; 396 if ($id_attrib !== null) { 397 $tag .= 'id="' . $id_attrib . '" '; 398 } 399 if ($class_attrib !== null) { 400 $tag .= 'class="' . $class_attrib . '" '; 401 } 402 if (isset($hover_name) && isset($hover_url)) { 403 $tag .= 'onmouseover="this.src=\'' . $hover_url . '\'; return true;"'; 404 $tag .= 'onmouseout="this.src=\'' . $image_url . '\'; return true;" '; 405 } 406 $tag .= '/>'; 407 } 408 409 return $tag; 410 } 411 412 /** 413 * _find_image 414 * 415 * Does the finding image thing. match svg first over png 416 * @param string $name 417 * @return string 418 */ 419 private static function _find_image($name) 420 { 421 if (isset(self::$_image_cache[$name])) { 422 return self::$_image_cache[$name]; 423 } 424 425 $path = AmpConfig::get('theme_path') . '/images/'; 426 $filesearch = glob(__DIR__ . '/../../../public/' . $path . $name . '.{svg,png}', GLOB_BRACE); 427 if (empty($filesearch)) { 428 // if the theme is missing an image. fall back to default images folder 429 $filename = $name . '.png'; 430 $path = '/images/'; 431 } else { 432 $filename = pathinfo($filesearch[0], 2); 433 } 434 $url = AmpConfig::get('web_path') . $path . $filename; 435 // cache the url so you don't need to keep searching 436 self::$_image_cache[$name] = $url; 437 438 return $url; 439 } 440 441 /** 442 * Show the requested template file 443 */ 444 public function show(string $template, array $context = []): void 445 { 446 extract($context); 447 448 require_once self::find_template($template); 449 } 450 451 public function showFooter(): void 452 { 453 static::show_footer(); 454 } 455 456 public function showHeader(): void 457 { 458 require_once self::find_template('header.inc.php'); 459 } 460 461 /** 462 * show_footer 463 * 464 * Shows the footer template and possibly profiling info. 465 * 466 * @deprecated use non-static version 467 */ 468 public static function show_footer() 469 { 470 if (!defined("TABLE_RENDERED")) { 471 show_table_render(); 472 } 473 474 $plugins = Plugin::get_plugins('display_on_footer'); 475 foreach ($plugins as $plugin_name) { 476 $plugin = new Plugin($plugin_name); 477 if ($plugin->load(Core::get_global('user'))) { 478 $plugin->_plugin->display_on_footer(); 479 } 480 } 481 482 require_once self::find_template('footer.inc.php'); 483 if (Core::get_request('profiling') !== '') { 484 Dba::show_profile(); 485 } 486 } 487 488 public function showBoxTop(string $title = '', string $class = ''): void 489 { 490 static::show_box_top($title, $class); 491 } 492 493 public function showBoxBottom(): void 494 { 495 static::show_box_bottom(); 496 } 497 498 /** 499 * show_box_top 500 * 501 * This shows the top of the box. 502 * @param string $title 503 * @param string $class 504 * 505 * @deprecated Use non-static version 506 */ 507 public static function show_box_top($title = '', $class = '') 508 { 509 require self::find_template('show_box_top.inc.php'); 510 } 511 512 /** 513 * show_box_bottom 514 * 515 * This shows the bottom of the box 516 * 517 * @deprecated Use non-static version 518 */ 519 public static function show_box_bottom() 520 { 521 require self::find_template('show_box_bottom.inc.php'); 522 } 523 524 /** 525 * This shows the query stats 526 */ 527 public function showQueryStats(): void 528 { 529 require self::find_template('show_query_stats.inc.php'); 530 } 531 532 public static function show_custom_style() 533 { 534 if (AmpConfig::get('custom_login_background')) { 535 echo "<style> body { background-position: center; background-size: cover; background-image: url('" . AmpConfig::get('custom_login_background') . "') !important; }</style>"; 536 } 537 538 if (AmpConfig::get('custom_login_logo')) { 539 echo "<style>#loginPage #headerlogo, #registerPage #headerlogo { background-image: url('" . AmpConfig::get('custom_login_logo') . "') !important; }</style>"; 540 } 541 542 $favicon = AmpConfig::get('custom_favicon') ?: AmpConfig::get('web_path') . "/favicon.ico"; 543 echo "<link rel='shortcut icon' href='" . $favicon . "' />\n"; 544 } 545 546 /** 547 * update_text 548 * 549 * Convenience function that, if the output is going to a browser, 550 * blarfs JS to do a fancy update. Otherwise it just outputs the text. 551 * @param string $field 552 * @param $value 553 */ 554 public static function update_text($field, $value) 555 { 556 if (defined('API')) { 557 return; 558 } 559 if (defined('CLI')) { 560 echo $value . "\n"; 561 562 return; 563 } 564 565 static $update_id = 1; 566 567 if (defined('SSE_OUTPUT')) { 568 echo "id: " . $update_id . "\n"; 569 echo "data: displayNotification('" . json_encode($value) . "', 5000)\n\n"; 570 } else { 571 if (!empty($field)) { 572 echo "<script>updateText('" . $field . "', '" . json_encode($value) . "');</script>\n"; 573 } else { 574 echo "<br />" . $value . "<br /><br />\n"; 575 } 576 } 577 578 ob_flush(); 579 flush(); 580 $update_id++; 581 } 582 583 /** 584 * get_logo_url 585 * 586 * Get the custom logo or logo relating to your theme color 587 * @param string $color 588 * @return string 589 */ 590 public static function get_logo_url($color = null) 591 { 592 if (AmpConfig::get('custom_logo')) { 593 return AmpConfig::get('custom_logo'); 594 } 595 if ($color !== null) { 596 return AmpConfig::get('web_path') . AmpConfig::get('theme_path') . '/images/ampache-' . $color . '.png'; 597 } 598 599 return AmpConfig::get('web_path') . AmpConfig::get('theme_path') . '/images/ampache-' . AmpConfig::get('theme_color') . '.png'; 600 } 601 602 /** 603 * @param $type 604 * @return boolean 605 */ 606 public static function is_grid_view($type) 607 { 608 $isgv = true; 609 $name = 'browse_' . $type . '_grid_view'; 610 if (filter_has_var(INPUT_COOKIE, $name)) { 611 $isgv = ($_COOKIE[$name] == 'true'); 612 } 613 614 return $isgv; 615 } 616 617 /** 618 * shows a confirmation of an action 619 * 620 * @param string $title The Title of the message 621 * @param string $text The details of the message 622 * @param string $next_url Where to go next 623 * @param integer $cancel T/F show a cancel button that uses return_referer() 624 * @param string $form_name 625 * @param boolean $visible 626 */ 627 public function showConfirmation( 628 $title, 629 $text, 630 $next_url, 631 $cancel = 0, 632 $form_name = 'confirmation', 633 $visible = true 634 ): void { 635 $webPath = $this->configContainer->getWebPath(); 636 637 if (substr_count($next_url, $webPath)) { 638 $path = $next_url; 639 } else { 640 $path = sprintf('%s/%s', $webPath, $next_url); 641 } 642 643 require Ui::find_template('show_confirmation.inc.php'); 644 } 645 646 /** 647 * This function is used to escape user data that is getting redisplayed 648 * onto the page, it htmlentities the mojo 649 * This is the inverse of the scrub_in function 650 */ 651 public function scrubOut(?string $string): string 652 { 653 if ($string === null) { 654 return ''; 655 } 656 657 return htmlentities((string) $string, ENT_NOQUOTES, AmpConfig::get('site_charset')); 658 } 659 660 /** 661 * takes the key and then creates the correct type of input for updating it 662 */ 663 public function createPreferenceInput( 664 string $name, 665 $value 666 ) { 667 if (!Preference::has_access($name)) { 668 if ($value == '1') { 669 echo T_("Enabled"); 670 } elseif ($value == '0') { 671 echo T_("Disabled"); 672 } else { 673 if (preg_match('/_pass$/', $name) || preg_match('/_api_key$/', $name)) { 674 echo "******"; 675 } else { 676 echo $value; 677 } 678 } 679 680 return; 681 } // if we don't have access to it 682 683 switch ($name) { 684 case 'display_menu': 685 case 'download': 686 case 'quarantine': 687 case 'upload': 688 case 'access_list': 689 case 'lock_songs': 690 case 'xml_rpc': 691 case 'force_http_play': 692 case 'no_symlinks': 693 case 'use_auth': 694 case 'access_control': 695 case 'allow_stream_playback': 696 case 'allow_democratic_playback': 697 case 'allow_localplay_playback': 698 case 'demo_mode': 699 case 'condPL': 700 case 'rio_track_stats': 701 case 'rio_global_stats': 702 case 'direct_link': 703 case 'ajax_load': 704 case 'now_playing_per_user': 705 case 'show_played_times': 706 case 'use_original_year': 707 case 'hide_single_artist': 708 case 'hide_genres': 709 case 'show_skipped_times': 710 case 'show_license': 711 case 'song_page_title': 712 case 'subsonic_backend': 713 case 'webplayer_flash': 714 case 'webplayer_html5': 715 case 'allow_personal_info_now': 716 case 'allow_personal_info_recent': 717 case 'allow_personal_info_time': 718 case 'allow_personal_info_agent': 719 case 'ui_fixed': 720 case 'autoupdate': 721 case 'autoupdate_lastversion_new': 722 case 'webplayer_confirmclose': 723 case 'webplayer_pausetabs': 724 case 'stream_beautiful_url': 725 case 'share': 726 case 'share_social': 727 case 'broadcast_by_default': 728 case 'album_group': 729 case 'topmenu': 730 case 'demo_clear_sessions': 731 case 'show_donate': 732 case 'allow_upload': 733 case 'upload_subdir': 734 case 'upload_user_artist': 735 case 'upload_allow_edit': 736 case 'daap_backend': 737 case 'upnp_backend': 738 case 'album_release_type': 739 case 'home_moment_albums': 740 case 'home_moment_videos': 741 case 'home_recently_played': 742 case 'home_now_playing': 743 case 'browser_notify': 744 case 'allow_video': 745 case 'geolocation': 746 case 'webplayer_aurora': 747 case 'upload_allow_remove': 748 case 'webdav_backend': 749 case 'notify_email': 750 case 'libitem_contextmenu': 751 case 'upload_catalog_pattern': 752 case 'catalogfav_gridview': 753 case 'personalfav_display': 754 case 'ratingmatch_flags': 755 case 'catalog_check_duplicate': 756 case 'browse_filter': 757 case 'sidebar_light': 758 case 'cron_cache': 759 case 'show_lyrics': 760 case 'unique_playlist': 761 $is_true = ''; 762 $is_false = ''; 763 if ($value == '1') { 764 $is_true = "selected=\"selected\""; 765 } else { 766 $is_false = "selected=\"selected\""; 767 } 768 echo "<select name=\"$name\">\n"; 769 echo "\t<option value=\"1\" $is_true>" . T_('On') . "</option>\n"; 770 echo "\t<option value=\"0\" $is_false>" . T_('Off') . "</option>\n"; 771 echo "</select>\n"; 772 break; 773 case 'upload_catalog': 774 show_catalog_select('upload_catalog', $value, '', true); 775 break; 776 case 'play_type': 777 $is_stream = ''; 778 $is_localplay = ''; 779 $is_democratic = ''; 780 $is_web_player = ''; 781 switch ($value) { 782 case 'localplay': 783 $is_localplay = 'selected="selected"'; 784 break; 785 case 'democratic': 786 $is_democratic = 'selected="selected"'; 787 break; 788 case 'web_player': 789 $is_web_player = 'selected="selected"'; 790 break; 791 default: 792 $is_stream = 'selected="selected"'; 793 } 794 echo "<select name=\"$name\">\n"; 795 echo "\t<option value=\"\">" . T_('None') . "</option>\n"; 796 if (AmpConfig::get('allow_stream_playback')) { 797 echo "\t<option value=\"stream\" $is_stream>" . T_('Stream') . "</option>\n"; 798 } 799 if (AmpConfig::get('allow_democratic_playback')) { 800 echo "\t<option value=\"democratic\" $is_democratic>" . T_('Democratic') . "</option>\n"; 801 } 802 if (AmpConfig::get('allow_localplay_playback')) { 803 echo "\t<option value=\"localplay\" $is_localplay>" . T_('Localplay') . "</option>\n"; 804 } 805 echo "\t<option value=\"web_player\" $is_web_player>" . T_('Web Player') . "</option>\n"; 806 echo "</select>\n"; 807 break; 808 case 'playlist_type': 809 $var_name = $value . "_type"; 810 ${$var_name} = "selected=\"selected\""; 811 echo "<select name=\"$name\">\n"; 812 echo "\t<option value=\"m3u\" $m3u_type>" . T_('M3U') . "</option>\n"; 813 echo "\t<option value=\"simple_m3u\" $simple_m3u_type>" . T_('Simple M3U') . "</option>\n"; 814 echo "\t<option value=\"pls\" $pls_type>" . T_('PLS') . "</option>\n"; 815 echo "\t<option value=\"asx\" $asx_type>" . T_('Asx') . "</option>\n"; 816 echo "\t<option value=\"ram\" $ram_type>" . T_('RAM') . "</option>\n"; 817 echo "\t<option value=\"xspf\" $xspf_type>" . T_('XSPF') . "</option>\n"; 818 echo "</select>\n"; 819 break; 820 case 'lang': 821 $languages = get_languages(); 822 echo '<select name="' . $name . '">' . "\n"; 823 foreach ($languages as $lang => $tongue) { 824 $selected = ($lang == $value) ? 'selected="selected"' : ''; 825 echo "\t<option value=\"$lang\" " . $selected . ">$tongue</option>\n"; 826 } // end foreach 827 echo "</select>\n"; 828 break; 829 case 'localplay_controller': 830 $controllers = array_keys(LocalPlayTypeEnum::TYPE_MAPPING); 831 echo "<select name=\"$name\">\n"; 832 echo "\t<option value=\"\">" . T_('None') . "</option>\n"; 833 foreach ($controllers as $controller) { 834 if (!LocalPlay::is_enabled($controller)) { 835 continue; 836 } 837 $is_selected = ''; 838 if ($value == $controller) { 839 $is_selected = 'selected="selected"'; 840 } 841 echo "\t<option value=\"" . $controller . "\" $is_selected>" . ucfirst($controller) . "</option>\n"; 842 } // end foreach 843 echo "</select>\n"; 844 break; 845 case 'ratingmatch_stars': 846 $is_0 = ''; 847 $is_1 = ''; 848 $is_2 = ''; 849 $is_3 = ''; 850 $is_4 = ''; 851 $is_5 = ''; 852 if ($value == 0) { 853 $is_0 = 'selected="selected"'; 854 } elseif ($value == 1) { 855 $is_1 = 'selected="selected"'; 856 } elseif ($value == 2) { 857 $is_2 = 'selected="selected"'; 858 } elseif ($value == 3) { 859 $is_3 = 'selected="selected"'; 860 } elseif ($value == 4) { 861 $is_4 = 'selected="selected"'; 862 } elseif ($value == 4) { 863 $is_5 = 'selected="selected"'; 864 } 865 echo "<select name=\"$name\">\n"; 866 echo "<option value=\"0\" $is_0>" . T_('Disabled') . "</option>\n"; 867 echo "<option value=\"1\" $is_1>" . T_('1 Star') . "</option>\n"; 868 echo "<option value=\"2\" $is_2>" . T_('2 Stars') . "</option>\n"; 869 echo "<option value=\"3\" $is_3>" . T_('3 Stars') . "</option>\n"; 870 echo "<option value=\"4\" $is_4>" . T_('4 Stars') . "</option>\n"; 871 echo "<option value=\"5\" $is_5>" . T_('5 Stars') . "</option>\n"; 872 echo "</select>\n"; 873 break; 874 case 'localplay_level': 875 $is_user = ''; 876 $is_admin = ''; 877 $is_manager = ''; 878 if ($value == '25') { 879 $is_user = 'selected="selected"'; 880 } elseif ($value == '100') { 881 $is_admin = 'selected="selected"'; 882 } elseif ($value == '50') { 883 $is_manager = 'selected="selected"'; 884 } 885 echo "<select name=\"$name\">\n"; 886 echo "<option value=\"0\">" . T_('Disabled') . "</option>\n"; 887 echo "<option value=\"25\" $is_user>" . T_('User') . "</option>\n"; 888 echo "<option value=\"50\" $is_manager>" . T_('Manager') . "</option>\n"; 889 echo "<option value=\"100\" $is_admin>" . T_('Admin') . "</option>\n"; 890 echo "</select>\n"; 891 break; 892 case 'theme_name': 893 $themes = get_themes(); 894 echo "<select name=\"$name\">\n"; 895 foreach ($themes as $theme) { 896 $is_selected = ""; 897 if ($value == $theme['path']) { 898 $is_selected = "selected=\"selected\""; 899 } 900 echo "\t<option value=\"" . $theme['path'] . "\" $is_selected>" . $theme['name'] . "</option>\n"; 901 } // foreach themes 902 echo "</select>\n"; 903 break; 904 case 'theme_color': 905 // This include a two-step configuration (first change theme and save, then change theme color and save) 906 $theme_cfg = get_theme(AmpConfig::get('theme_name')); 907 if ($theme_cfg !== null) { 908 echo "<select name=\"$name\">\n"; 909 foreach ($theme_cfg['colors'] as $color) { 910 $is_selected = ""; 911 if ($value == strtolower((string) $color)) { 912 $is_selected = "selected=\"selected\""; 913 } 914 echo "\t<option value=\"" . strtolower((string) $color) . "\" $is_selected>" . $color . "</option>\n"; 915 } // foreach themes 916 echo "</select>\n"; 917 } 918 break; 919 case 'playlist_method': 920 ${$value} = ' selected="selected"'; 921 echo "<select name=\"$name\">\n"; 922 echo "\t<option value=\"send\"$send>" . T_('Send on Add') . "</option>\n"; 923 echo "\t<option value=\"send_clear\"$send_clear>" . T_('Send and Clear on Add') . "</option>\n"; 924 echo "\t<option value=\"clear\"$clear>" . T_('Clear on Send') . "</option>\n"; 925 echo "\t<option value=\"default\"$default>" . T_('Default') . "</option>\n"; 926 echo "</select>\n"; 927 break; 928 case 'transcode': 929 ${$value} = ' selected="selected"'; 930 echo "<select name=\"$name\">\n"; 931 echo "\t<option value=\"never\"$never>" . T_('Never') . "</option>\n"; 932 echo "\t<option value=\"default\"$default>" . T_('Default') . "</option>\n"; 933 echo "\t<option value=\"always\"$always>" . T_('Always') . "</option>\n"; 934 echo "</select>\n"; 935 break; 936 case 'album_sort': 937 $is_sort_year_asc = ''; 938 $is_sort_year_desc = ''; 939 $is_sort_name_asc = ''; 940 $is_sort_name_desc = ''; 941 $is_sort_default = ''; 942 if ($value == 'year_asc') { 943 $is_sort_year_asc = 'selected="selected"'; 944 } elseif ($value == 'year_desc') { 945 $is_sort_year_desc = 'selected="selected"'; 946 } elseif ($value == 'name_asc') { 947 $is_sort_name_asc = 'selected="selected"'; 948 } elseif ($value == 'name_desc') { 949 $is_sort_name_desc = 'selected="selected"'; 950 } else { 951 $is_sort_default = 'selected="selected"'; 952 } 953 954 echo "<select name=\"$name\">\n"; 955 echo "\t<option value=\"default\" $is_sort_default>" . T_('Default') . "</option>\n"; 956 echo "\t<option value=\"year_asc\" $is_sort_year_asc>" . T_('Year ascending') . "</option>\n"; 957 echo "\t<option value=\"year_desc\" $is_sort_year_desc>" . T_('Year descending') . "</option>\n"; 958 echo "\t<option value=\"name_asc\" $is_sort_name_asc>" . T_('Name ascending') . "</option>\n"; 959 echo "\t<option value=\"name_desc\" $is_sort_name_desc>" . T_('Name descending') . "</option>\n"; 960 echo "</select>\n"; 961 break; 962 case 'disabled_custom_metadata_fields': 963 $ids = explode(',', $value); 964 $options = array(); 965 $fieldRepository = new MetadataField(); 966 foreach ($fieldRepository->findAll() as $field) { 967 $selected = in_array($field->getId(), $ids) ? ' selected="selected"' : ''; 968 $options[] = '<option value="' . $field->getId() . '"' . $selected . '>' . $field->getName() . '</option>'; 969 } 970 echo '<select multiple size="5" name="' . $name . '[]">' . implode("\n", $options) . '</select>'; 971 break; 972 case 'personalfav_playlist': 973 case 'personalfav_smartlist': 974 $ids = explode(',', $value); 975 $options = array(); 976 $playlists = ($name == 'personalfav_smartlist') ? Playlist::get_details('search') : Playlist::get_details(); 977 if (!empty($playlists)) { 978 foreach ($playlists as $list_id => $list_name) { 979 $selected = in_array($list_id, $ids) ? ' selected="selected"' : ''; 980 $options[] = '<option value="' . $list_id . '"' . $selected . '>' . $list_name . '</option>'; 981 } 982 echo '<select multiple size="5" name="' . $name . '[]">' . implode("\n", $options) . '</select>'; 983 } 984 break; 985 case 'lastfm_grant_link': 986 case 'librefm_grant_link': 987 // construct links for granting access Ampache application to Last.fm and Libre.fm 988 $plugin_name = ucfirst(str_replace('_grant_link', '', $name)); 989 $plugin = new Plugin($plugin_name); 990 $url = $plugin->_plugin->url; 991 $api_key = rawurlencode(AmpConfig::get('lastfm_api_key')); 992 $callback = rawurlencode(AmpConfig::get('web_path') . '/preferences.php?tab=plugins&action=grant&plugin=' . $plugin_name); 993 /* HINT: Plugin Name */ 994 echo "<a href='$url/api/auth/?api_key=$api_key&cb=$callback'>" . Ui::get_icon('plugin', sprintf(T_("Click to grant %s access to Ampache"), $plugin_name)) . '</a>'; 995 break; 996 default: 997 if (preg_match('/_pass$/', $name)) { 998 echo '<input type="password" name="' . $name . '" value="******" />'; 999 } else { 1000 echo '<input type="text" name="' . $name . '" value="' . $value . '" />'; 1001 } 1002 break; 1003 } 1004 } 1005 1006 /** 1007 * This shows the preference box for the preferences pages. 1008 * 1009 * @var array<string, mixed> $preferences 1010 */ 1011 public function showPreferenceBox(array $preferences): void 1012 { 1013 $this->show( 1014 'show_preference_box.inc.php', 1015 [ 1016 'preferences' => $preferences, 1017 'ui' => $this 1018 ] 1019 ); 1020 } 1021} 1022