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 25use Ampache\Config\AmpConfig; 26use Ampache\Repository\Model\Artist; 27use Ampache\Repository\Model\Catalog; 28use Ampache\Repository\Model\Metadata\Repository\MetadataField; 29use Ampache\Repository\Model\Playlist; 30use Ampache\Repository\Model\Plugin; 31use Ampache\Repository\Model\Preference; 32use Ampache\Repository\Model\TVShow_Season; 33use Ampache\Module\Api\Xml_Data; 34use Ampache\Module\Authorization\Access; 35use Ampache\Module\Authorization\AccessLevelEnum; 36use Ampache\Module\Authorization\Check\PrivilegeCheckerInterface; 37use Ampache\Module\Playback\Localplay\LocalPlay; 38use Ampache\Module\Playback\Localplay\LocalPlayTypeEnum; 39use Ampache\Module\Playback\Stream; 40use Ampache\Module\System\Core; 41use Ampache\Module\System\Dba; 42use Ampache\Module\System\Session; 43use Ampache\Module\Util\Ui; 44use Gettext\Translator; 45use Psr\Log\LoggerInterface; 46 47/** 48 * set_memory_limit 49 * This function attempts to change the php memory limit using init_set. 50 * Will never reduce it below the current setting. 51 * @param $new_limit 52 */ 53function set_memory_limit($new_limit) 54{ 55 $current_limit = ini_get('memory_limit'); 56 if ($current_limit == -1) { 57 return; 58 } 59 60 $current_limit = Ui::unformat_bytes($current_limit); 61 $new_limit = Ui::unformat_bytes($new_limit); 62 63 if ($current_limit < $new_limit) { 64 ini_set('memory_limit', $new_limit); 65 } 66} // set_memory_limit 67 68/** 69 * scrub_in 70 * Run on inputs, stuff that might get stuck in our db 71 * @param string|array $input 72 * @return string|array 73 */ 74function scrub_in($input) 75{ 76 if (!is_array($input)) { 77 return stripslashes(htmlspecialchars(strip_tags((string) $input), ENT_NOQUOTES, AmpConfig::get('site_charset'))); 78 } else { 79 $results = array(); 80 foreach ($input as $item) { 81 $results[] = scrub_in((string) $item); 82 } 83 84 return $results; 85 } 86} // scrub_in 87 88/** 89 * scrub_out 90 * This function is used to escape user data that is getting redisplayed 91 * onto the page, it htmlentities the mojo 92 * This is the inverse of the scrub_in function 93 * @param string|null $string 94 * @return string 95 * 96 * @deprecated see Ui::scrubOut 97 */ 98function scrub_out($string) 99{ 100 if ($string === null) { 101 return ''; 102 } 103 104 return htmlentities((string) $string, ENT_NOQUOTES, AmpConfig::get('site_charset')); 105} // scrub_out 106 107/** 108 * unhtmlentities 109 * Undoes htmlentities() 110 * @param string $string 111 * @return string 112 */ 113function unhtmlentities($string) 114{ 115 return html_entity_decode((string) $string, ENT_QUOTES, AmpConfig::get('site_charset')); 116} // unhtmlentities 117 118/** 119 * make_bool 120 * This takes a value and returns what we consider to be the correct boolean 121 * value. We need a special function because PHP considers "false" to be true. 122 * 123 * @param string $string 124 * @return boolean 125 */ 126function make_bool($string) 127{ 128 if ($string === null) { 129 return false; 130 } 131 if (strcasecmp((string) $string, 'false') == 0 || $string == '0') { 132 return false; 133 } 134 135 return (bool) $string; 136} // make_bool 137 138/** 139 * invert_bool 140 * This returns the opposite of what you've got 141 * @param $value 142 * @return boolean 143 */ 144function invert_bool($value) 145{ 146 return make_bool($value) ? false : true; 147} // invert_bool 148 149/** 150 * get_languages 151 * This function does a dir of ./locale and pulls the names of the 152 * different languages installed, this means that all you have to do 153 * is drop one in and it will show up on the context menu. It returns 154 * in the form of an array of names 155 * @return array 156 */ 157function get_languages() 158{ 159 /* Open the locale directory */ 160 $handle = opendir(__DIR__ . '/../../locale'); 161 162 if (!is_resource($handle)) { 163 debug_event('general.lib', 'Error unable to open locale directory', 1); 164 } 165 166 $results = array(); 167 168 while (false !== ($file = readdir($handle))) { 169 $full_file = __DIR__ . '/../../locale/' . $file; 170 171 /* Check to see if it's a directory */ 172 if (is_dir($full_file) && substr($file, 0, 1) != '.' && $file != 'base') { 173 switch ($file) { 174 case 'af_ZA': 175 $name = 'Afrikaans'; 176 break; /* Afrikaans */ 177 case 'bg_BG': 178 $name = 'Български'; 179 break; /* Bulgarian */ 180 case 'ca_ES': 181 $name = 'Català'; 182 break; /* Catalan */ 183 case 'cs_CZ': 184 $name = 'Česky'; 185 break; /* Czech */ 186 case 'da_DK': 187 $name = 'Dansk'; 188 break; /* Danish */ 189 case 'de_DE': 190 $name = 'Deutsch'; 191 break; /* German */ 192 case 'el_GR': 193 $name = 'Greek'; 194 break; /* Greek */ 195 case 'en_GB': 196 $name = 'English (UK)'; 197 break; /* English */ 198 case 'en_US': 199 $name = 'English (US)'; 200 break; /* English */ 201 case 'es_AR': 202 $name = 'Español (AR)'; 203 break; /* Spanish */ 204 case 'es_ES': 205 $name = 'Español'; 206 break; /* Spanish */ 207 case 'es_MX': 208 $name = 'Español (MX)'; 209 break; /* Spanish */ 210 case 'et_EE': 211 $name = 'Eesti'; 212 break; /* Estonian */ 213 case 'eu_ES': 214 $name = 'Euskara'; 215 break; /* Basque */ 216 case 'fi_FI': 217 $name = 'Suomi'; 218 break; /* Finnish */ 219 case 'fr_FR': 220 $name = 'Français'; 221 break; /* French */ 222 case 'ga_IE': 223 $name = 'Gaeilge'; 224 break; /* Irish */ 225 case 'hu_HU': 226 $name = 'Magyar'; 227 break; /* Hungarian */ 228 case 'id_ID': 229 $name = 'Indonesia'; 230 break; /* Indonesian */ 231 case 'is_IS': 232 $name = 'Icelandic'; 233 break; /* Icelandic */ 234 case 'it_IT': 235 $name = 'Italiano'; 236 break; /* Italian */ 237 case 'ja_JP': 238 $name = '日本語'; 239 break; /* Japanese */ 240 case 'ko_KR': 241 $name = '한국말'; 242 break; /* Korean */ 243 case 'lt_LT': 244 $name = 'Lietuvių'; 245 break; /* Lithuanian */ 246 case 'lv_LV': 247 $name = 'Latviešu'; 248 break; /* Latvian */ 249 case 'nb_NO': 250 $name = 'Norsk'; 251 break; /* Norwegian */ 252 case 'nl_NL': 253 $name = 'Nederlands'; 254 break; /* Dutch */ 255 case 'no_NO': 256 $name = 'Norsk bokmål'; 257 break; /* Norwegian */ 258 case 'pl_PL': 259 $name = 'Polski'; 260 break; /* Polish */ 261 case 'pt_BR': 262 $name = 'Português Brasileiro'; 263 break; /* Portuguese */ 264 case 'pt_PT': 265 $name = 'Português'; 266 break; /* Portuguese */ 267 case 'ro_RO': 268 $name = 'Română'; 269 break; /* Romanian */ 270 case 'ru_RU': 271 $name = 'Русский'; 272 break; /* Russian */ 273 case 'sk_SK': 274 $name = 'Slovenčina'; 275 break; /* Slovak */ 276 case 'sl_SI': 277 $name = 'Slovenščina'; 278 break; /* Slovenian */ 279 case 'sr_CS': 280 $name = 'Srpski'; 281 break; /* Serbian */ 282 case 'sv_SE': 283 $name = 'Svenska'; 284 break; /* Swedish */ 285 case 'tr_TR': 286 $name = 'Türkçe'; 287 break; /* Turkish */ 288 case 'uk_UA': 289 $name = 'Українська'; 290 break; /* Ukrainian */ 291 case 'vi_VN': 292 $name = 'Tiếng Việt'; 293 break; /* Vietnamese */ 294 case 'zh_CN': 295 $name = '简体中文'; 296 break; /* Chinese (simplified)*/ 297 case 'zh_TW': 298 $name = '繁體中文'; 299 break; /* Chinese (traditional)*/ 300 /* These languages are right to left. */ 301 case 'ar_SA': 302 $name = 'العربية'; 303 break; /* Arabic */ 304 case 'he_IL': 305 $name = 'עברית'; 306 break; /* Hebrew */ 307 case 'fa_IR': 308 $name = 'فارسي'; 309 break; /* Farsi */ 310 default: 311 $name = sprintf( 312 /* HINT: File */ 313 T_('Unknown %s'), '(' . $file . ')'); 314 break; 315 } // end switch 316 317 $results[$file] = $name; 318 } 319 } // end while 320 321 // Sort the list of languages by country code 322 ksort($results); 323 324 // Prepend English (US) 325 $results = array("en_US" => "English (US)") + $results; 326 327 return $results; 328} // get_languages 329 330/** 331 * is_rtl 332 * This checks whether to be a Right-To-Left language. 333 * @param $locale 334 * @return boolean 335 */ 336function is_rtl($locale) 337{ 338 return in_array($locale, array("he_IL", "fa_IR", "ar_SA")); 339} 340 341/** 342 * translate_pattern_code 343 * This just contains a keyed array which it checks against to give you the 344 * 'tag' name that said pattern code corresponds to. It returns false if nothing 345 * is found. 346 * @param $code 347 * @return string|false 348 */ 349function translate_pattern_code($code) 350{ 351 $code_array = array('%A' => 'album', 352 '%a' => 'artist', 353 '%c' => 'comment', 354 '%C' => 'catalog_number', 355 '%T' => 'track', 356 '%d' => 'disk', 357 '%g' => 'genre', 358 '%t' => 'title', 359 '%y' => 'year', 360 '%Y' => 'original_year', 361 '%r' => 'release_type', 362 '%b' => 'barcode', 363 '%o' => 'zz_other'); 364 365 if (isset($code_array[$code])) { 366 return $code_array[$code]; 367 } 368 369 return false; 370} // translate_pattern_code 371 372// Declare apache_request_headers and getallheaders if it don't exists (PHP <= 5.3 + FastCGI) 373if (!function_exists('apache_request_headers')) { 374 /** 375 * @return array 376 */ 377 function apache_request_headers() 378 { 379 $headers = array(); 380 foreach ($_SERVER as $name => $value) { 381 if (substr($name, 0, 5) == 'HTTP_') { 382 $name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5))))); 383 $headers[$name] = $value; 384 } else { 385 if ($name == "CONTENT_TYPE") { 386 $headers["Content-Type"] = $value; 387 } else { 388 if ($name == "CONTENT_LENGTH") { 389 $headers["Content-Length"] = $value; 390 } 391 } 392 } 393 } 394 395 return $headers; 396 } 397} 398if (!function_exists('getallheaders')) { 399 /** 400 * @return array 401 */ 402 function getallheaders() 403 { 404 return apache_request_headers(); 405 } 406} 407 408/** 409 * @return string 410 */ 411function get_current_path() 412{ 413 if (strlen((string) $_SERVER['PHP_SELF'])) { 414 $root = $_SERVER['PHP_SELF']; 415 } else { 416 $root = $_SERVER['REQUEST_URI']; 417 } 418 419 return (string) $root; 420} 421 422/** 423 * @return string 424 */ 425function get_web_path() 426{ 427 $root = get_current_path(); 428 429 return (string) preg_replace('#(.*)/(\w+\.php)$#', '$1', $root); 430} 431 432/** 433 * get_datetime 434 * @param integer $time 435 * @param string $date_format 436 * @param string $time_format 437 * @param string $overwrite 438 * @return string 439 */ 440function get_datetime($time, $date_format = 'short', $time_format = 'short', $overwrite = '') 441{ 442 // allow time or date only 443 $date_type = ($date_format == 'none') ? IntlDateFormatter::NONE : IntlDateFormatter::SHORT; 444 $time_type = ($time_format == 'none') ? IntlDateFormatter::NONE : IntlDateFormatter::SHORT; 445 // if no override is set but you have a custom_datetime 446 $pattern = ($overwrite == '') ? (string) AmpConfig::get('custom_datetime', '') : $overwrite; 447 448 // get your locale and set the date based on that, unless you have 'custom_datetime set' 449 $locale = AmpConfig::get('lang', 'en_US'); 450 $format = new IntlDateFormatter($locale, $date_type, $time_type, null, null, $pattern); 451 452 return $format->format($time); 453} 454 455/** 456 * check_config_values 457 * checks to make sure that they have at least set the needed variables 458 * @param array $conf 459 * @return boolean 460 */ 461function check_config_values($conf) 462{ 463 if (!$conf['database_hostname']) { 464 return false; 465 } 466 if (!$conf['database_name']) { 467 return false; 468 } 469 if (!$conf['database_username']) { 470 return false; 471 } 472 /* Don't check for password to support mysql socket auth 473 * if (!$conf['database_password']) { 474 return false; 475 }*/ 476 if (!$conf['session_length']) { 477 return false; 478 } 479 if (!$conf['session_name']) { 480 return false; 481 } 482 if (!isset($conf['session_cookielife'])) { 483 return false; 484 } 485 if (!isset($conf['session_cookiesecure'])) { 486 return false; 487 } 488 if (isset($conf['debug'])) { 489 if (!isset($conf['log_path'])) { 490 return false; 491 } 492 } 493 494 return true; 495} // check_config_values 496 497/** 498 * @param string $val 499 * @return integer|string 500 */ 501function return_bytes($val) 502{ 503 $val = trim((string) $val); 504 $last = strtolower((string) $val[strlen((string) $val) - 1]); 505 switch ($last) { 506 // The 'G' modifier is available since PHP 5.1.0 507 case 'g': 508 $val *= 1024; 509 // Intentional break fall-through 510 case 'm': 511 $val *= 1024; 512 // Intentional break fall-through 513 case 'k': 514 $val *= 1024; 515 break; 516 } 517 518 return $val; 519} 520 521/** 522 * check_config_writable 523 * This checks whether we can write the config file 524 * @return boolean 525 */ 526function check_config_writable() 527{ 528 // file eixsts && is writable, or dir is writable 529 return ((file_exists(__DIR__ . '/../../config/ampache.cfg.php') && is_writable(__DIR__ . '/../../config/ampache.cfg.php')) 530 || (!file_exists(__DIR__ . '/../../config/ampache.cfg.php') && is_writeable(__DIR__ . '/../../config/'))); 531} 532 533/** 534 * @return boolean 535 */ 536function check_htaccess_channel_writable() 537{ 538 return ((file_exists(__DIR__ . '/../../public/channel/.htaccess') && is_writable(__DIR__ . '/../../public/channel/.htaccess')) 539 || (!file_exists(__DIR__ . '/../../public/channel/.htaccess') && is_writeable(__DIR__ . '/../../public/channel/'))); 540} 541 542/** 543 * @return boolean 544 */ 545function check_htaccess_rest_writable() 546{ 547 return ((file_exists(__DIR__ . '/../../public/rest/.htaccess') && is_writable(__DIR__ . '/../../public/rest/.htaccess')) 548 || (!file_exists(__DIR__ . '/../../public/rest/.htaccess') && is_writeable(__DIR__ . '/../../public/rest/'))); 549} 550 551/** 552 * @return boolean 553 */ 554function check_htaccess_play_writable() 555{ 556 return ((file_exists(__DIR__ . '/../../public/play/.htaccess') && is_writable(__DIR__ . '/../../public/play/.htaccess')) 557 || (!file_exists(__DIR__ . '/../../public/play/.htaccess') && is_writeable(__DIR__ . '/../../public/play/'))); 558} 559 560/** 561 * debug_result 562 * Convenience function to format the output. 563 * @param string|boolean $status 564 * @param string $value 565 * @param string $comment 566 * @return string 567 */ 568function debug_result($status = false, $value = null, $comment = '') 569{ 570 $class = $status ? 'success' : 'danger'; 571 572 if ($value === null) { 573 $value = $status ? T_('OK') : T_('Error'); 574 } 575 576 return '<button type="button" class="btn btn-' . $class . '">' . scrub_out($value) . 577 '</span> <em>' . $comment . '</em></button>'; 578} 579 580/** 581 * ampache_error_handler 582 * 583 * An error handler for ampache that traps as many errors as it can and logs 584 * them. 585 * @param $errno 586 * @param $errstr 587 * @param $errfile 588 * @param $errline 589 */ 590function ampache_error_handler($errno, $errstr, $errfile, $errline) 591{ 592 $level = 1; 593 594 switch ($errno) { 595 case E_WARNING: 596 $error_name = 'Runtime Error'; 597 break; 598 case E_COMPILE_WARNING: 599 case E_NOTICE: 600 case E_CORE_WARNING: 601 $error_name = 'Warning'; 602 $level = 6; 603 break; 604 case E_ERROR: 605 $error_name = 'Fatal run-time Error'; 606 break; 607 case E_PARSE: 608 $error_name = 'Parse Error'; 609 break; 610 case E_CORE_ERROR: 611 $error_name = 'Fatal Core Error'; 612 break; 613 case E_COMPILE_ERROR: 614 $error_name = 'Zend run-time Error'; 615 break; 616 case E_STRICT: 617 $error_name = "Strict Error"; 618 break; 619 default: 620 $error_name = "Error"; 621 $level = 2; 622 break; 623 } // end switch 624 625 // List of things that should only be displayed if they told us to turn 626 // on the firehose 627 $ignores = array( 628 // We know var is deprecated, shut up 629 'var: Deprecated. Please use the public/private/protected modifiers', 630 // getid3 spews errors, yay! 631 'getimagesize() [', 632 'Non-static method getid3', 633 'Assigning the return value of new by reference is deprecated', 634 // The XML-RPC lib is broken (kinda) 635 'used as offset, casting to integer' 636 ); 637 638 foreach ($ignores as $ignore) { 639 if (strpos($errstr, $ignore) !== false) { 640 $error_name = 'Ignored ' . $error_name; 641 $level = 7; 642 } 643 } 644 645 if (error_reporting() == 0) { 646 // Ignored, probably via @. But not really, so use the super-sekrit level 647 $level = 7; 648 } 649 650 if (strpos($errstr, 'date.timezone') !== false) { 651 $error_name = 'Warning'; 652 $errstr = 'You have not set a valid timezone (date.timezone) in your php.ini file. This may cause display issues with dates. This warning is non-critical and not caused by Ampache.'; 653 } 654 655 $log_line = "[$error_name] $errstr in file $errfile($errline)"; 656 debug_event('log.lib', $log_line, $level, '', 'ampache'); 657} 658 659/** 660 * debug_event 661 * This function is called inside Ampache, it's actually a wrapper for the 662 * log_event. It checks config for debug and debug_level and only 663 * calls log event if both requirements are met. 664 * @param string $type 665 * @param string $message 666 * @param integer $level 667 * @param string $file 668 * @param string $username 669 * @return boolean 670 * 671 * @deprecated Use LegacyLogger 672 */ 673function debug_event($type, $message, $level, $file = '', $username = '') 674{ 675 if (!$username && Core::get_global('user')) { 676 $username = Core::get_global('user')->username; 677 } 678 679 global $dic; 680 $logger = $dic->get(LoggerInterface::class); 681 682 // If the message is multiple lines, make multiple log lines 683 foreach (explode("\n", (string) $message) as $line) { 684 $logger->log( 685 $level, 686 $line, 687 [ 688 'username' => $username, 689 'event_type' => $type 690 ] 691 ); 692 } 693 694 return true; 695} // debug_event 696 697/** 698 * @param $action 699 * @param $catalogs 700 * @param array $options 701 */ 702function catalog_worker($action, $catalogs = null, $options = null) 703{ 704 if (AmpConfig::get('ajax_load')) { 705 $sse_url = AmpConfig::get('web_path') . "/server/sse.server.php?worker=catalog&action=" . $action . "&catalogs=" . urlencode(json_encode($catalogs)); 706 if ($options) { 707 $sse_url .= "&options=" . urlencode(json_encode($_POST)); 708 } 709 sse_worker($sse_url); 710 } else { 711 Catalog::process_action($action, $catalogs, $options); 712 } 713} 714 715/** 716 * @param string $url 717 */ 718function sse_worker($url) 719{ 720 echo '<script>'; 721 echo "sse_worker('$url');"; 722 echo "</script>\n"; 723} 724 725/** 726 * return_referer 727 * returns the script part of the referer address passed by the web browser 728 * this is not %100 accurate. Also because this is not passed by us we need 729 * to clean it up, take the filename then check for a /admin/ and dump the rest 730 * @return string 731 */ 732function return_referer() 733{ 734 $referer = $_SERVER['HTTP_REFERER']; 735 if (substr($referer, -1) == '/') { 736 $file = 'index.php'; 737 } else { 738 $file = basename($referer); 739 /* Strip off the filename */ 740 $referer = substr($referer, 0, strlen((string) $referer) - strlen((string) $file)); 741 } 742 743 if (substr($referer, strlen((string) $referer) - 6, 6) == 'admin/') { 744 $file = 'admin/' . $file; 745 } 746 747 return $file; 748} // return_referer 749 750/** 751 * get_location 752 * This function gets the information about a person's current location. 753 * This is used for A) sidebar highlighting & submenu showing and B) titlebar 754 * information. It returns an array of information about what they are currently 755 * doing. 756 * Possible array elements 757 * ['title'] Text name for the page 758 * ['page'] actual page name 759 * ['section'] name of the section we are in, admin, browse etc (submenu) 760 */ 761function get_location() 762{ 763 $location = array(); 764 765 if (strlen((string) $_SERVER['PHP_SELF'])) { 766 $source = $_SERVER['PHP_SELF']; 767 } else { 768 $source = $_SERVER['REQUEST_URI']; 769 } 770 771 /* Sanatize the $_SERVER['PHP_SELF'] variable */ 772 $source = str_replace(AmpConfig::get('raw_web_path'), "", $source); 773 $location['page'] = preg_replace("/^\/(.+\.php)\/?.*/", "$1", $source); 774 775 switch ($location['page']) { 776 case 'index.php': 777 $location['title'] = T_('Home'); 778 break; 779 case 'upload.php': 780 $location['title'] = T_('Upload'); 781 break; 782 case 'localplay.php': 783 $location['title'] = T_('Localplay'); 784 break; 785 case 'randomplay.php': 786 $location['title'] = T_('Random Play'); 787 break; 788 case 'playlist.php': 789 $location['title'] = T_('Playlist'); 790 break; 791 case 'search.php': 792 $location['title'] = T_('Search'); 793 break; 794 case 'preferences.php': 795 $location['title'] = T_('Preferences'); 796 break; 797 case 'admin/catalog.php': 798 case 'admin/index.php': 799 $location['title'] = T_('Admin-Catalog'); 800 $location['section'] = 'admin'; 801 break; 802 case 'admin/users.php': 803 $location['title'] = T_('Admin-User Management'); 804 $location['section'] = 'admin'; 805 break; 806 case 'admin/mail.php': 807 $location['title'] = T_('Admin-Mail Users'); 808 $location['section'] = 'admin'; 809 break; 810 case 'admin/access.php': 811 $location['title'] = T_('Admin-Manage Access Lists'); 812 $location['section'] = 'admin'; 813 break; 814 case 'admin/preferences.php': 815 $location['title'] = T_('Admin-Site Preferences'); 816 $location['section'] = 'admin'; 817 break; 818 case 'admin/modules.php': 819 $location['title'] = T_('Admin-Manage Modules'); 820 $location['section'] = 'admin'; 821 break; 822 case 'browse.php': 823 $location['title'] = T_('Browse Music'); 824 $location['section'] = 'browse'; 825 break; 826 case 'albums.php': 827 $location['title'] = T_('Albums'); 828 $location['section'] = 'browse'; 829 break; 830 case 'artists.php': 831 $location['title'] = T_('Artists'); 832 $location['section'] = 'browse'; 833 break; 834 case 'stats.php': 835 $location['title'] = T_('Statistics'); 836 break; 837 default: 838 $location['title'] = ''; 839 break; 840 } // switch on raw page location 841 842 return $location; 843} // get_location 844 845/** 846 * show_album_select 847 * This displays a select of every album that we've got in Ampache (which can be 848 * hella long). It's used by the Edit page and takes a $name and a $album_id 849 * @param string $name 850 * @param integer $album_id 851 * @param boolean $allow_add 852 * @param integer $song_id 853 * @param boolean $allow_none 854 * @param string $user 855 */ 856function show_album_select($name, $album_id = 0, $allow_add = false, $song_id = 0, $allow_none = false, $user = null) 857{ 858 static $album_id_cnt = 0; 859 860 // Generate key to use for HTML element ID 861 if ($song_id) { 862 $key = "album_select_" . $song_id; 863 } else { 864 $key = "album_select_c" . ++$album_id_cnt; 865 } 866 867 $sql = "SELECT `album`.`id`, `album`.`name`, `album`.`prefix`, `disk` FROM `album`"; 868 $params = array(); 869 if ($user !== null) { 870 $sql .= "INNER JOIN `artist` ON `artist`.`id` = `album`.`album_artist` WHERE `album`.`album_artist` IS NOT NULL AND `artist`.`user` = ? "; 871 $params[] = $user; 872 } 873 $sql .= "ORDER BY `album`.`name`"; 874 $db_results = Dba::read($sql, $params); 875 $count = Dba::num_rows($db_results); 876 877 // Added ID field so we can easily observe this element 878 echo "<select name=\"$name\" id=\"$key\">\n"; 879 880 if ($allow_none) { 881 echo "\t<option value=\"-2\"></option>\n"; 882 } 883 884 while ($row = Dba::fetch_assoc($db_results)) { 885 $selected = ''; 886 $album_name = trim((string) $row['prefix'] . " " . $row['name']); 887 if (!AmpConfig::get('album_group') && (int) $count > 1) { 888 $album_name .= " [" . T_('Disk') . " " . $row['disk'] . "]"; 889 } 890 if ($row['id'] == $album_id) { 891 $selected = "selected=\"selected\""; 892 } 893 894 echo "\t<option value=\"" . $row['id'] . "\" $selected>" . scrub_out($album_name) . "</option>\n"; 895 } // end while 896 897 if ($allow_add) { 898 // Append additional option to the end with value=-1 899 echo "\t<option value=\"-1\">" . T_('Add New') . "...</option>\n"; 900 } 901 902 echo "</select>\n"; 903 904 if ($count === 0) { 905 echo "<script>check_inline_song_edit('" . $name . "', " . $song_id . ");</script>\n"; 906 } 907} // show_album_select 908 909/** 910 * show_artist_select 911 * This is the same as show_album_select except it's *gasp* for artists! How 912 * inventive! 913 * @param string $name 914 * @param integer $artist_id 915 * @param boolean $allow_add 916 * @param integer $song_id 917 * @param boolean $allow_none 918 * @param integer $user_id 919 */ 920function show_artist_select($name, $artist_id = 0, $allow_add = false, $song_id = 0, $allow_none = false, $user_id = null) 921{ 922 static $artist_id_cnt = 0; 923 // Generate key to use for HTML element ID 924 if ($song_id) { 925 $key = $name . "_select_" . $song_id; 926 } else { 927 $key = $name . "_select_c" . ++$artist_id_cnt; 928 } 929 930 $sql = "SELECT `id`, `name`, `prefix` FROM `artist` "; 931 $params = array(); 932 if ($user_id !== null) { 933 $sql .= "WHERE `user` = ? "; 934 $params[] = $user_id; 935 } 936 $sql .= "ORDER BY `name`"; 937 $db_results = Dba::read($sql, $params); 938 $count = Dba::num_rows($db_results); 939 940 echo "<select name=\"$name\" id=\"$key\">\n"; 941 942 if ($allow_none) { 943 echo "\t<option value=\"-2\"></option>\n"; 944 } 945 946 while ($row = Dba::fetch_assoc($db_results)) { 947 $selected = ''; 948 $artist_name = trim((string) $row['prefix'] . " " . $row['name']); 949 if ($row['id'] == $artist_id) { 950 $selected = "selected=\"selected\""; 951 } 952 953 echo "\t<option value=\"" . $row['id'] . "\" $selected>" . scrub_out($artist_name) . "</option>\n"; 954 } // end while 955 956 if ($allow_add) { 957 // Append additional option to the end with value=-1 958 echo "\t<option value=\"-1\">" . T_('Add New') . "...</option>\n"; 959 } 960 961 echo "</select>\n"; 962 963 if ($count === 0) { 964 echo "<script>check_inline_song_edit('" . $name . "', " . $song_id . ");</script>\n"; 965 } 966} // show_artist_select 967 968/** 969 * show_tvshow_select 970 * This is the same as show_album_select except it's *gasp* for tvshows! How 971 * inventive! 972 * @param string $name 973 * @param integer $tvshow_id 974 * @param boolean $allow_add 975 * @param integer $season_id 976 * @param boolean $allow_none 977 */ 978function show_tvshow_select($name, $tvshow_id = 0, $allow_add = false, $season_id = 0, $allow_none = false) 979{ 980 static $tvshow_id_cnt = 0; 981 // Generate key to use for HTML element ID 982 if ($season_id) { 983 $key = $name . "_select_" . $season_id; 984 } else { 985 $key = $name . "_select_c" . ++$tvshow_id_cnt; 986 } 987 988 echo "<select name=\"$name\" id=\"$key\">\n"; 989 990 if ($allow_none) { 991 echo "\t<option value=\"-2\"></option>\n"; 992 } 993 994 $sql = "SELECT `id`, `name` FROM `tvshow` ORDER BY `name`"; 995 $db_results = Dba::read($sql); 996 997 while ($row = Dba::fetch_assoc($db_results)) { 998 $selected = ''; 999 if ($row['id'] == $tvshow_id) { 1000 $selected = "selected=\"selected\""; 1001 } 1002 1003 echo "\t<option value=\"" . $row['id'] . "\" $selected>" . scrub_out($row['name']) . "</option>\n"; 1004 } // end while 1005 1006 if ($allow_add) { 1007 // Append additional option to the end with value=-1 1008 echo "\t<option value=\"-1\">" . T_('Add New') . "...</option>\n"; 1009 } 1010 1011 echo "</select>\n"; 1012} // show_tvshow_select 1013 1014/** 1015 * @param string $name 1016 * @param $season_id 1017 * @param boolean $allow_add 1018 * @param integer $video_id 1019 * @param boolean $allow_none 1020 * @return boolean 1021 */ 1022function show_tvshow_season_select($name, $season_id, $allow_add = false, $video_id = 0, $allow_none = false) 1023{ 1024 if (!$season_id) { 1025 return false; 1026 } 1027 $season = new TVShow_Season($season_id); 1028 1029 static $season_id_cnt = 0; 1030 // Generate key to use for HTML element ID 1031 if ($video_id) { 1032 $key = $name . "_select_" . $video_id; 1033 } else { 1034 $key = $name . "_select_c" . ++$season_id_cnt; 1035 } 1036 1037 echo "<select name=\"$name\" id=\"$key\">\n"; 1038 1039 if ($allow_none) { 1040 echo "\t<option value=\"-2\"></option>\n"; 1041 } 1042 1043 $sql = "SELECT `id`, `season_number` FROM `tvshow_season` WHERE `tvshow` = ? ORDER BY `season_number`"; 1044 $db_results = Dba::read($sql, array($season->tvshow)); 1045 1046 while ($row = Dba::fetch_assoc($db_results)) { 1047 $selected = ''; 1048 if ($row['id'] == $season_id) { 1049 $selected = "selected=\"selected\""; 1050 } 1051 1052 echo "\t<option value=\"" . $row['id'] . "\" $selected>" . scrub_out($row['season_number']) . "</option>\n"; 1053 } // end while 1054 1055 if ($allow_add) { 1056 // Append additional option to the end with value=-1 1057 echo "\t<option value=\"-1\">" . T_('Add New') . "...</option>\n"; 1058 } 1059 1060 echo "</select>\n"; 1061 1062 return true; 1063} 1064 1065/** 1066 * show_catalog_select 1067 * Yet another one of these buggers. this shows a drop down of all of your 1068 * catalogs. 1069 * @param string $name 1070 * @param integer $catalog_id 1071 * @param string $style 1072 * @param boolean $allow_none 1073 * @param string $filter_type 1074 */ 1075function show_catalog_select($name, $catalog_id, $style = '', $allow_none = false, $filter_type = '') 1076{ 1077 echo "<select name=\"$name\" style=\"$style\">\n"; 1078 1079 $params = array(); 1080 $sql = "SELECT `id`, `name` FROM `catalog` "; 1081 if (!empty($filter_type)) { 1082 $sql .= "WHERE `gather_types` = ?"; 1083 $params[] = $filter_type; 1084 } 1085 $sql .= "ORDER BY `name`"; 1086 $db_results = Dba::read($sql, $params); 1087 $results = array(); 1088 while ($row = Dba::fetch_assoc($db_results)) { 1089 $results[] = $row; 1090 } 1091 1092 if ($allow_none) { 1093 echo "\t<option value=\"-1\">" . T_('None') . "</option>\n"; 1094 } 1095 if (empty($results) && !empty($filter_type)) { 1096 /* HINT: Requested object string/id/type ("album", "myusername", "some song title", 1298376) */ 1097 echo "\t<option value=\"\"selected=\"selected\">" . sprintf(T_('Not Found: %s'), $filter_type) . "</option>\n"; 1098 } 1099 1100 foreach ($results as $row) { 1101 $selected = ''; 1102 if ($row['id'] == (string) $catalog_id) { 1103 $selected = "selected=\"selected\""; 1104 } 1105 1106 echo "\t<option value=\"" . $row['id'] . "\" $selected>" . scrub_out($row['name']) . "</option>\n"; 1107 } // end while 1108 1109 echo "</select>\n"; 1110} // show_catalog_select 1111 1112/** 1113 * show_album_select 1114 * This displays a select of every album that we've got in Ampache (which can be 1115 * hella long). It's used by the Edit page and takes a $name and a $album_id 1116 * @param string $name 1117 * @param integer $license_id 1118 * @param integer $song_id 1119 */ 1120function show_license_select($name, $license_id = 0, $song_id = 0) 1121{ 1122 static $license_id_cnt = 0; 1123 1124 // Generate key to use for HTML element ID 1125 if ($song_id > 0) { 1126 $key = "license_select_" . $song_id; 1127 } else { 1128 $key = "license_select_c" . ++$license_id_cnt; 1129 } 1130 1131 // Added ID field so we can easily observe this element 1132 echo "<select name=\"$name\" id=\"$key\">\n"; 1133 1134 $sql = "SELECT `id`, `name`, `description`, `external_link` FROM `license` ORDER BY `name`"; 1135 $db_results = Dba::read($sql); 1136 1137 while ($row = Dba::fetch_assoc($db_results)) { 1138 $selected = ''; 1139 if ($row['id'] == $license_id) { 1140 $selected = "selected=\"selected\""; 1141 } 1142 1143 echo "\t<option value=\"" . $row['id'] . "\" $selected"; 1144 if (!empty($row['description'])) { 1145 echo " title=\"" . addslashes($row['description']) . "\""; 1146 } 1147 if (!empty($row['external_link'])) { 1148 echo " data-link=\"" . $row['external_link'] . "\""; 1149 } 1150 echo ">" . $row['name'] . "</option>\n"; 1151 } // end while 1152 1153 echo "</select>\n"; 1154 echo "<a href=\"javascript:show_selected_license_link('" . $key . "');\">" . T_('View License') . "</a>"; 1155} // show_license_select 1156 1157/** 1158 * show_user_select 1159 * This one is for users! shows a select/option statement so you can pick a user 1160 * to blame 1161 * @param string $name 1162 * @param string $selected 1163 * @param string $style 1164 */ 1165function show_user_select($name, $selected = '', $style = '') 1166{ 1167 echo "<select name=\"$name\" style=\"$style\">\n"; 1168 echo "\t<option value=\"\">" . T_('All') . "</option>\n"; 1169 1170 $sql = "SELECT `id`, `username`, `fullname` FROM `user` ORDER BY `fullname`"; 1171 $db_results = Dba::read($sql); 1172 1173 while ($row = Dba::fetch_assoc($db_results)) { 1174 $select_txt = ''; 1175 if ($row['id'] == $selected) { 1176 $select_txt = 'selected="selected"'; 1177 } 1178 // If they don't have a full name, revert to the username 1179 $row['fullname'] = $row['fullname'] ? $row['fullname'] : $row['username']; 1180 1181 echo "\t<option value=\"" . $row['id'] . "\" $select_txt>" . scrub_out($row['fullname']) . "</option>\n"; 1182 } // end while users 1183 1184 echo "</select>\n"; 1185} // show_user_select 1186 1187 1188function xoutput_headers() 1189{ 1190 $output = (Core::get_request('xoutput') !== '') ? Core::get_request('xoutput') : 'xml'; 1191 if ($output == 'xml') { 1192 header("Content-type: text/xml; charset=" . AmpConfig::get('site_charset')); 1193 header("Content-Disposition: attachment; filename=ajax.xml"); 1194 } else { 1195 header("Content-type: application/json; charset=" . AmpConfig::get('site_charset')); 1196 } 1197 1198 header("Expires: Tuesday, 27 Mar 1984 05:00:00 GMT"); 1199 header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); 1200 header("Cache-Control: no-store, no-cache, must-revalidate"); 1201 header("Pragma: no-cache"); 1202} 1203 1204/** 1205 * @param array $array 1206 * @param boolean $callback 1207 * @param string $type 1208 * @return false|mixed|string 1209 */ 1210function xoutput_from_array($array, $callback = false, $type = '') 1211{ 1212 $output = (Core::get_request('xoutput') !== '') ? Core::get_request('xoutput') : 'xml'; 1213 if ($output == 'xml') { 1214 return Xml_Data::output_xml_from_array($array, $callback, $type); 1215 } elseif ($output == 'raw') { 1216 $outputnode = Core::get_request('xoutputnode'); 1217 1218 return $array[$outputnode]; 1219 } else { 1220 return json_encode($array); 1221 } 1222} 1223 1224/** 1225 * toggle_visible 1226 * This is identical to the javascript command that it actually calls 1227 * @param $element 1228 */ 1229function toggle_visible($element) 1230{ 1231 echo '<script>'; 1232 echo "toggleVisible('$element');"; 1233 echo "</script>\n"; 1234} // toggle_visible 1235 1236/** 1237 * display_notification 1238 * Show a javascript notification to the user 1239 * @param string $message 1240 * @param integer $timeout 1241 */ 1242function display_notification($message, $timeout = 5000) 1243{ 1244 echo "<script>"; 1245 echo "displayNotification('" . addslashes(json_encode($message, JSON_UNESCAPED_UNICODE)) . "', " . $timeout . ");"; 1246 echo "</script>\n"; 1247} 1248 1249/** 1250 * print_bool 1251 * This function takes a boolean value and then prints out a friendly text 1252 * message. 1253 * @param $value 1254 * @return string 1255 */ 1256function print_bool($value) 1257{ 1258 if ($value) { 1259 $string = '<span class="item_on">' . T_('On') . '</span>'; 1260 } else { 1261 $string = '<span class="item_off">' . T_('Off') . '</span>'; 1262 } 1263 1264 return $string; 1265} // print_bool 1266 1267/** 1268 * show_now_playing 1269 * This shows the Now Playing templates and does some garbage collection 1270 * this should really be somewhere else 1271 */ 1272function show_now_playing() 1273{ 1274 Session::garbage_collection(); 1275 Stream::garbage_collection(); 1276 1277 $web_path = AmpConfig::get('web_path'); 1278 $results = Stream::get_now_playing(); 1279 require_once Ui::find_template('show_now_playing.inc.php'); 1280} // show_now_playing 1281 1282/** 1283 * @param boolean $render 1284 * @param boolean $force 1285 */ 1286function show_table_render($render = false, $force = false) 1287{ 1288 // Include table render javascript only once 1289 if ($force || !defined('TABLE_RENDERED')) { 1290 define('TABLE_RENDERED', 1); ?> 1291 <?php if (isset($render) && $render) { ?> 1292 <script>sortPlaylistRender();</script> 1293 <?php 1294 } 1295 } 1296} 1297 1298/** 1299 * load_gettext 1300 * Sets up our local gettext settings. 1301 * 1302 * @return boolean 1303 */ 1304function load_gettext() 1305{ 1306 $lang = AmpConfig::get('lang'); 1307 $mopath = __DIR__ . '/../../locale/' . $lang . '/LC_MESSAGES/messages.mo'; 1308 1309 $gettext = new Translator(); 1310 if (file_exists($mopath)) { 1311 $translations = Gettext\Translations::fromMoFile($mopath); 1312 $gettext->loadTranslations($translations); 1313 } 1314 $gettext->register(); 1315 1316 return true; 1317} // load_gettext 1318 1319/** 1320 * T_ 1321 * Translate string 1322 * @param string $msgid 1323 * @return string 1324 */ 1325function T_($msgid) 1326{ 1327 if (function_exists('__')) { 1328 return __($msgid); 1329 } 1330 1331 return $msgid; 1332} 1333 1334/** 1335 * @param $original 1336 * @param $plural 1337 * @param $value 1338 * @return mixed 1339 */ 1340function nT_($original, $plural, $value) 1341{ 1342 if (function_exists('n__')) { 1343 return n__($original, $plural, $value); 1344 } 1345 1346 return $plural; 1347} 1348 1349/** 1350 * get_themes 1351 * this looks in /themes and pulls all of the 1352 * theme.cfg.php files it can find and returns an 1353 * array of the results 1354 * @return array 1355 */ 1356function get_themes() 1357{ 1358 /* Open the themes dir and start reading it */ 1359 $handle = opendir(__DIR__ . '/../../public/themes'); 1360 1361 if (!is_resource($handle)) { 1362 debug_event('themes', 'Failed to open /themes directory', 2); 1363 1364 return array(); 1365 } 1366 1367 $results = array(); 1368 while (($file = readdir($handle)) !== false) { 1369 if ((string) $file !== '.' && (string) $file !== '..') { 1370 debug_event('themes', "Checking $file", 5); 1371 $cfg = get_theme($file); 1372 if ($cfg !== null) { 1373 $results[$cfg['name']] = $cfg; 1374 } 1375 } 1376 } // end while directory 1377 // Sort by the theme name 1378 ksort($results); 1379 1380 return $results; 1381} // get_themes 1382 1383/** 1384 * @function get_theme 1385 * @discussion get a single theme and read the config file 1386 * then return the results 1387 * @param string $name 1388 * @return array|boolean|false|mixed|null 1389 */ 1390function get_theme($name) 1391{ 1392 static $_mapcache = array(); 1393 1394 if (strlen((string) $name) < 1) { 1395 return false; 1396 } 1397 1398 $name = strtolower((string) $name); 1399 1400 if (isset($_mapcache[$name])) { 1401 return $_mapcache[$name]; 1402 } 1403 1404 $config_file = __DIR__ . "/../../public/themes/" . $name . "/theme.cfg.php"; 1405 if (file_exists($config_file)) { 1406 $results = parse_ini_file($config_file); 1407 $results['path'] = $name; 1408 $results['base'] = explode(',', (string) $results['base']); 1409 $nbbases = count($results['base']); 1410 for ($count = 0; $count < $nbbases; $count++) { 1411 $results['base'][$count] = explode('|', $results['base'][$count]); 1412 } 1413 $results['colors'] = explode(',', (string) $results['colors']); 1414 } else { 1415 $results = null; 1416 } 1417 $_mapcache[$name] = $results; 1418 1419 return $results; 1420} // get_theme 1421 1422/** 1423 * @function get_theme_author 1424 * @discussion returns the author of this theme 1425 * @param string $theme_name 1426 * @return string 1427 */ 1428function get_theme_author($theme_name) 1429{ 1430 $theme_path = __DIR__ . '/../../public/themes/' . $theme_name . '/theme.cfg.php'; 1431 $results = read_config($theme_path); 1432 1433 return $results['author']; 1434} // get_theme_author 1435 1436/** 1437 * @function theme_exists 1438 * @discussion this function checks to make sure that a theme actually exists 1439 * @param string $theme_name 1440 * @return boolean 1441 */ 1442function theme_exists($theme_name) 1443{ 1444 $theme_path = __DIR__ . '/../../public/themes/' . $theme_name . '/theme.cfg.php'; 1445 1446 if (!file_exists($theme_path)) { 1447 return false; 1448 } 1449 1450 return true; 1451} // theme_exists 1452 1453/** 1454 * Used in graph class als format string 1455 * 1456 * @see \Ampache\Module\Util\Graph 1457 * 1458 * @param $value 1459 * @return string 1460 */ 1461function pGraph_Yformat_bytes($value) 1462{ 1463 return Ui::format_bytes($value); 1464} 1465 1466function get_mime_from_image($data): string 1467{ 1468 switch ($data) { 1469 case substr($data, 0, 4) == 'ffd8': 1470 return "image/jpeg"; 1471 case '89504E47': 1472 return "image/png"; 1473 case '47494638': 1474 return "image/gif"; 1475 case substr($data,0, 4) == '424d': 1476 return 'image/bmp'; 1477 default: 1478 return 'image/jpeg'; 1479 } 1480} 1481 1482/** 1483 * @deprecated Will be removed 1484 */ 1485function canEditArtist( 1486 Artist $artist, 1487 int $userId 1488): bool { 1489 if (AmpConfig::get('upload_allow_edit')) { 1490 if ($artist->user !== null && $userId == $artist->user) { 1491 return true; 1492 } 1493 } 1494 1495 global $dic; 1496 1497 return $dic->get(PrivilegeCheckerInterface::class)->check( 1498 AccessLevelEnum::TYPE_INTERFACE, 1499 AccessLevelEnum::LEVEL_CONTENT_MANAGER 1500 ); 1501} 1502