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 = '&#x0411;&#x044a;&#x043b;&#x0433;&#x0430;&#x0440;&#x0441;&#x043a;&#x0438;';
179                    break; /* Bulgarian */
180                case 'ca_ES':
181                    $name = 'Catal&#224;';
182                    break; /* Catalan */
183                case 'cs_CZ':
184                    $name = '&#x010c;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&#241;ol (AR)';
203                    break; /* Spanish */
204                case 'es_ES':
205                    $name = 'Espa&#241;ol';
206                    break; /* Spanish */
207                case 'es_MX':
208                    $name = 'Espa&#241;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&#231;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 = '&#x65e5;&#x672c;&#x8a9e;';
239                    break; /* Japanese */
240                case 'ko_KR':
241                    $name = '&#xd55c;&#xad6d;&#xb9d0;';
242                    break; /* Korean */
243                case 'lt_LT':
244                    $name = 'Lietuvi&#371;';
245                    break; /* Lithuanian */
246                case 'lv_LV':
247                    $name = 'Latvie&#353;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&#229;l';
257                    break; /* Norwegian */
258                case 'pl_PL':
259                    $name = 'Polski';
260                    break; /* Polish */
261                case 'pt_BR':
262                    $name = 'Portugu&#234;s Brasileiro';
263                    break; /* Portuguese */
264                case 'pt_PT':
265                    $name = 'Portugu&#234;s';
266                    break; /* Portuguese */
267                case 'ro_RO':
268                    $name = 'Rom&#226;n&#259;';
269                    break; /* Romanian */
270                case 'ru_RU':
271                    $name = '&#1056;&#1091;&#1089;&#1089;&#1082;&#1080;&#1081;';
272                    break; /* Russian */
273                case 'sk_SK':
274                    $name = 'Sloven&#269;ina';
275                    break; /* Slovak */
276                case 'sl_SI':
277                    $name = 'Sloven&#353;&#269;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&#252;rk&#231;e';
287                    break; /* Turkish */
288                case 'uk_UA':
289                    $name = 'Українська';
290                    break; /* Ukrainian */
291                case 'vi_VN':
292                    $name = 'Ti&#7871;ng Vi&#7879;t';
293                    break; /* Vietnamese */
294                case 'zh_CN':
295                    $name = '&#31616;&#20307;&#20013;&#25991;';
296                    break; /* Chinese (simplified)*/
297                case 'zh_TW':
298                    $name = '&#32321;&#39636;&#20013;&#25991;';
299                    break; /* Chinese (traditional)*/
300                /* These languages are right to left. */
301                case 'ar_SA':
302                    $name = '&#1575;&#1604;&#1593;&#1585;&#1576;&#1610;&#1577;';
303                    break; /* Arabic */
304                case 'he_IL':
305                    $name = '&#1506;&#1489;&#1512;&#1497;&#1514;';
306                    break; /* Hebrew */
307                case 'fa_IR':
308                    $name = '&#1601;&#1575;&#1585;&#1587;&#1610;';
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