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\Repository\Model;
26
27use Ampache\Module\Authorization\Access;
28use Ampache\Module\System\Dba;
29use Ampache\Config\AmpConfig;
30use Ampache\Module\System\Core;
31
32/**
33 * This handles all of the preference stuff for Ampache
34 */
35class Preference extends database_object
36{
37    protected const DB_TABLENAME = 'preference';
38
39    /**
40     * This array contains System preferences that can (should) not be edited or deleted from the api
41     */
42    public const SYSTEM_LIST = array(
43        'ajax_load',
44        'album_group',
45        'album_release_type',
46        'album_release_type_sort',
47        'album_sort',
48        'allow_democratic_playback',
49        'allow_localplay_playback',
50        'allow_personal_info_agent',
51        'allow_personal_info_now',
52        'allow_personal_info_recent',
53        'allow_personal_info_time',
54        'allow_stream_playback',
55        'allow_upload',
56        'allow_video',
57        'autoupdate',
58        'autoupdate_lastcheck',
59        'autoupdate_lastversion',
60        'autoupdate_lastversion_new',
61        'broadcast_by_default',
62        'browse_filter',
63        'browser_notify',
64        'browser_notify_timeout',
65        'catalog_check_duplicate',
66        'cron_cache',
67        'custom_blankalbum',
68        'custom_blankmovie',
69        'custom_datetime',
70        'custom_favicon',
71        'custom_login_backgound',
72        'custom_login_logo',
73        'custom_logo',
74        'custom_text_footer',
75        'daap_backend',
76        'daap_pass',
77        'demo_clear_sessions',
78        'direct_play_limit',
79        'disabled_custom_metadata_fields',
80        'disabled_custom_metadata_fields_input',
81        'download',
82        'force_http_play',
83        'geolocation',
84        'home_moment_albums',
85        'home_moment_videos',
86        'home_now_playing',
87        'home_recently_played',
88        'httpq_active',
89        'lang',
90        'lastfm_challenge',
91        'lastfm_grant_link',
92        'libitem_browse_alpha',
93        'libitem_contextmenu',
94        'localplay_controller',
95        'localplay_level',
96        'lock_songs',
97        'mpd_active',
98        'notify_email',
99        'now_playing_per_user',
100        'offset_limit',
101        'playlist_method',
102        'playlist_type',
103        'play_type',
104        'podcast_keep',
105        'podcast_new_download',
106        'popular_threshold',
107        'rate_limit',
108        'share',
109        'share_expire',
110        'show_donate',
111        'show_lyrics',
112        'show_played_times',
113        'show_skipped_times',
114        'sidebar_light',
115        'site_title',
116        'slideshow_time',
117        'song_page_title',
118        'stats_threshold',
119        'stream_beautiful_url',
120        'subsonic_backend',
121        'theme_color',
122        'theme_name',
123        'topmenu',
124        'transcode',
125        'transcode_bitrate',
126        'ui_fixed',
127        'unique_playlist',
128        'upload_allow_edit',
129        'upload_allow_remove',
130        'upload_catalog',
131        'upload_catalog_pattern',
132        'upload_script',
133        'upload_subdir',
134        'upload_user_artist',
135        'upnp_backend',
136        'webdav_backend',
137        'webplayer_aurora',
138        'webplayer_confirmclose',
139        'webplayer_flash',
140        'webplayer_html5',
141        'webplayer_pausetabs'
142    );
143
144    /**
145     * __constructor
146     * This does nothing... amazing isn't it!
147     */
148    private function __construct()
149    {
150        // Rien a faire
151    } // __construct
152
153    /**
154     * get_by_user
155     * Return a preference for specific user identifier
156     * @param integer $user_id
157     * @param string $pref_name
158     * @return integer|string
159     */
160    public static function get_by_user($user_id, $pref_name)
161    {
162        //debug_event(self::class, 'Getting preference {'.$pref_name.'} for user identifier {'.$user_id.'}...', 5);
163        $user_id   = (int) Dba::escape($user_id);
164        $pref_name = Dba::escape($pref_name);
165        $pref_id   = self::id_from_name($pref_name);
166
167        if (parent::is_cached('get_by_user-' . $pref_name, $user_id)) {
168            return (parent::get_from_cache('get_by_user-' . $pref_name, $user_id))['value'];
169        }
170
171        $sql        = "SELECT `value` FROM `user_preference` WHERE `preference`='$pref_id' AND `user`='$user_id'";
172        $db_results = Dba::read($sql);
173        if (Dba::num_rows($db_results) < 1) {
174            $sql        = "SELECT `value` FROM `user_preference` WHERE `preference`='$pref_id' AND `user`='-1'";
175            $db_results = Dba::read($sql);
176        }
177        $data = Dba::fetch_assoc($db_results);
178
179        parent::add_to_cache('get_by_user-' . $pref_name, $user_id, $data);
180
181        return $data['value'];
182    } // get_by_user
183
184    /**
185     * update
186     * This updates a single preference from the given name or id
187     * @param string $preference
188     * @param integer $user_id
189     * @param array|string $value
190     * @param boolean $applytoall
191     * @param boolean $applytodefault
192     * @return boolean
193     */
194    public static function update($preference, $user_id, $value, $applytoall = false, $applytodefault = false)
195    {
196        // First prepare
197        if (!is_numeric($preference)) {
198            $pref_id = self::id_from_name($preference);
199            $name    = $preference;
200        } else {
201            $pref_id = $preference;
202            $name    = self::name_from_id($preference);
203        }
204        if (is_array($value)) {
205            $value = implode(',', $value);
206        }
207        $params = array($value, $pref_id);
208
209        if ($applytoall && Access::check('interface', 100)) {
210            $user_check = "";
211        } else {
212            $user_check = "AND `user` = ?";
213            $params[]   = $user_id;
214        }
215
216        if ($applytodefault && Access::check('interface', 100)) {
217            $sql = "UPDATE `preference` SET `value` = ? WHERE `id`= ?";
218            Dba::write($sql, $params);
219        }
220
221        if (self::has_access($name)) {
222            $sql = "UPDATE `user_preference` SET `value` = ?  WHERE `preference` = ? $user_check";
223            Dba::write($sql, $params);
224            self::clear_from_session();
225
226            parent::remove_from_cache('get_by_user', $user_id);
227
228            return true;
229        } else {
230            debug_event(self::class, Core::get_global('user') ? Core::get_global('user')->username : '???' . ' attempted to update ' . $name . ' but does not have sufficient permissions', 3);
231        }
232
233        return false;
234    } // update
235
236    /**
237     * update_level
238     * This takes a preference ID and updates the level required to update it (performed by an admin)
239     * @param $preference
240     * @param $level
241     * @return boolean
242     */
243    public static function update_level($preference, $level)
244    {
245        // First prepare
246        if (!is_numeric($preference)) {
247            $preference_id = self::id_from_name($preference);
248        } else {
249            $preference_id = $preference;
250        }
251
252        $preference_id = Dba::escape($preference_id);
253        $level         = Dba::escape($level);
254
255        $sql = "UPDATE `preference` SET `level`='$level' WHERE `id`='$preference_id'";
256        Dba::write($sql);
257
258        return true;
259    } // update_level
260
261    /**
262     * update_all
263     * This takes a preference id and a value and updates all users with the new info
264     * @param integer $preference_id
265     * @param string $value
266     * @return boolean
267     */
268    public static function update_all($preference_id, $value)
269    {
270        $preference_id = (string)Dba::escape($preference_id);
271        $value         = (string)Dba::escape($value);
272
273        $sql = "UPDATE `user_preference` SET `value`='$value' WHERE `preference`='$preference_id'";
274        Dba::write($sql);
275
276        parent::clear_cache();
277
278        return true;
279    } // update_all
280
281    /**
282     * exists
283     * This just checks to see if a preference currently exists
284     * @param string $preference
285     * @return integer
286     */
287    public static function exists($preference)
288    {
289        // We assume it's the name
290        $name       = Dba::escape($preference);
291        $sql        = "SELECT * FROM `preference` WHERE `name`='$name'";
292        $db_results = Dba::read($sql);
293
294        return Dba::num_rows($db_results);
295    } // exists
296
297    /**
298     * has_access
299     * This checks to see if the current user has access to modify this preference
300     * as defined by the preference name
301     * @param $preference
302     * @return boolean
303     */
304    public static function has_access($preference)
305    {
306        // Nothing for those demo thugs
307        if (AmpConfig::get('demo_mode')) {
308            return false;
309        }
310
311        $preference = Dba::escape($preference);
312
313        $sql        = "SELECT `level` FROM `preference` WHERE `name`='$preference'";
314        $db_results = Dba::read($sql);
315        $data       = Dba::fetch_assoc($db_results);
316
317        if (Access::check('interface', $data['level'])) {
318            return true;
319        }
320
321        return false;
322    } // has_access
323
324    /**
325     * id_from_name
326     * This takes a name and returns the id
327     * @param string $name
328     * @return array|integer
329     */
330    public static function id_from_name($name)
331    {
332        $name = Dba::escape($name);
333
334        if (parent::is_cached('id_from_name', $name)) {
335            return (int)(parent::get_from_cache('id_from_name', $name))[0];
336        }
337
338        $sql        = "SELECT `id` FROM `preference` WHERE `name`='$name'";
339        $db_results = Dba::read($sql);
340        $row        = Dba::fetch_assoc($db_results);
341
342        parent::add_to_cache('id_from_name', $name, $row);
343
344        return (int)$row['id'];
345    } // id_from_name
346
347    /**
348     * name_from_id
349     * This returns the name from an id, it's the exact opposite
350     * of the function above it, amazing!
351     * @param $pref_id
352     * @return mixed
353     */
354    public static function name_from_id($pref_id)
355    {
356        $pref_id = Dba::escape($pref_id);
357
358        $sql        = "SELECT `name` FROM `preference` WHERE `id`='$pref_id'";
359        $db_results = Dba::read($sql);
360
361        $row = Dba::fetch_assoc($db_results);
362
363        return $row['name'];
364    } // name_from_id
365
366    /**
367     * get_categories
368     * This returns an array of the names of the different possible sections
369     * it ignores the 'internal' category
370     */
371    public static function get_categories()
372    {
373        $sql = "SELECT `preference`.`catagory` FROM `preference` GROUP BY `catagory` ORDER BY `catagory`";
374
375        $db_results = Dba::read($sql);
376        $results    = array();
377        while ($row = Dba::fetch_assoc($db_results)) {
378            if ($row['catagory'] != 'internal') {
379                $results[] = $row['catagory'];
380            }
381        } // end while
382
383        return $results;
384    } // get_categories
385
386    /**
387     * get_all
388     * This returns a nice flat array of all of the possible preferences for the specified user
389     * @param integer $user_id
390     * @return array
391     */
392    public static function get_all($user_id)
393    {
394        $user_id    = Dba::escape($user_id);
395        $user_limit = ($user_id != -1) ? "AND `preference`.`catagory` != 'system'" : "";
396
397        $sql = "SELECT `preference`.`id`, `preference`.`name`, `preference`.`description`, `preference`.`level`, `preference`.`type`, `preference`.`catagory`, `preference`.`subcatagory`, `user_preference`.`value` FROM `preference` INNER JOIN `user_preference` ON `user_preference`.`preference`=`preference`.`id` WHERE `user_preference`.`user` = ? AND `preference`.`catagory` != 'internal' $user_limit ORDER BY `preference`.`subcatagory`, `preference`.`description`";
398
399        $db_results = Dba::read($sql, array($user_id));
400        $results    = array();
401
402        while ($row = Dba::fetch_assoc($db_results)) {
403            $results[] = array(
404                'id' => $row['id'],
405                'name' => $row['name'],
406                'level' => $row['level'],
407                'description' => $row['description'],
408                'value' => $row['value'],
409                'type' => $row['type'],
410                'category' => $row['catagory'],
411                'subcategory' => $row['subcatagory']
412            );
413        }
414
415        return $results;
416    } // get_all
417
418    /**
419     * get
420     * This returns a nice flat array of all of the possible preferences for the specified user
421     * @param string $pref_name
422     * @param integer $user_id
423     * @return array
424     */
425    public static function get($pref_name, $user_id)
426    {
427        $user_id    = Dba::escape($user_id);
428        $user_limit = ($user_id != -1) ? "AND `preference`.`catagory` != 'system'" : "";
429
430        $sql = "SELECT `preference`.`id`, `preference`.`name`, `preference`.`description`, `preference`.`level`, `preference`.`type`, `preference`.`catagory`, `preference`.`subcatagory`, `user_preference`.`value` FROM `preference` INNER JOIN `user_preference` ON `user_preference`.`preference`=`preference`.`id` WHERE `preference`.`name` = ? AND `user_preference`.`user`= ? AND `preference`.`catagory` != 'internal' $user_limit ORDER BY `preference`.`subcatagory`, `preference`.`description`";
431
432        $db_results = Dba::read($sql, array($pref_name, $user_id));
433        $results    = array();
434
435        while ($row = Dba::fetch_assoc($db_results)) {
436            $results[] = array('id' => $row['id'], 'name' => $row['name'], 'level' => $row['level'], 'description' => $row['description'],
437                'value' => $row['value'], 'type' => $row['type'], 'category' => $row['catagory'], 'subcategory' => $row['subcatagory']);
438        }
439
440        return $results;
441    } // get
442
443    /**
444     * insert
445     * This inserts a new preference into the preference table
446     * it does NOT sync up the users, that should be done independently
447     * @param string $name
448     * @param string $description
449     * @param string|integer $default
450     * @param integer $level
451     * @param string $type
452     * @param string $category
453     * @param string $subcategory
454     * @return boolean
455     */
456    public static function insert($name, $description, $default, $level, $type, $category, $subcategory = null)
457    {
458        if ($subcategory !== null) {
459            $subcategory = strtolower((string)$subcategory);
460        }
461        $sql        = "INSERT INTO `preference` (`name`, `description`, `value`, `level`, `type`, `catagory`, `subcatagory`) VALUES (?, ?, ?, ?, ?, ?, ?)";
462        $db_results = Dba::write($sql, array($name, $description, $default, (int) $level, $type, $category, $subcategory));
463
464        if (!$db_results) {
465            return false;
466        }
467        $pref_id    = Dba::insert_id();
468        $params     = array($pref_id, $default);
469        $sql        = "INSERT INTO `user_preference` VALUES (-1, ?, ?)";
470        $db_results = Dba::write($sql, $params);
471        if (!$db_results) {
472            return false;
473        }
474        if ($category !== "system") {
475            $sql        = "INSERT INTO `user_preference` SELECT `user`.`id`, ?, ? FROM `user`";
476            $db_results = Dba::write($sql, $params);
477            if (!$db_results) {
478                return false;
479            }
480        }
481
482        return true;
483    } // insert
484
485    /**
486     * delete
487     * This deletes the specified preference, a name or a ID can be passed
488     * @param string|integer $preference
489     */
490    public static function delete($preference)
491    {
492        // First prepare
493        if (!is_numeric($preference)) {
494            $sql = "DELETE FROM `preference` WHERE `name` = ?";
495        } else {
496            $sql = "DELETE FROM `preference` WHERE `id` = ?";
497        }
498
499        Dba::write($sql, array($preference));
500
501        self::clean_preferences();
502    } // delete
503
504    /**
505     * rename
506     * This renames a preference in the database
507     * @param $old
508     * @param $new
509     */
510    public static function rename($old, $new)
511    {
512        $sql = "UPDATE `preference` SET `name` = ? WHERE `name` = ?";
513        Dba::write($sql, array($new, $old));
514    }
515
516    /**
517     * clean_preferences
518     * This removes any garbage
519     */
520    public static function clean_preferences()
521    {
522        // First remove garbage
523        $sql = "DELETE FROM `user_preference` USING `user_preference` LEFT JOIN `preference` ON `preference`.`id`=`user_preference`.`preference` WHERE `preference`.`id` IS NULL";
524        Dba::write($sql);
525    } // rebuild_preferences
526
527    /**
528     * fix_preferences
529     * This takes the preferences, explodes what needs to
530     * become an array and boolean everything
531     * @param $results
532     * @return array
533     */
534    public static function fix_preferences($results)
535    {
536        $arrays = array(
537            'auth_methods',
538            'getid3_tag_order',
539            'metadata_order',
540            'metadata_order_video',
541            'art_order',
542            'registration_display_fields',
543            'registration_mandatory_fields'
544        );
545
546        foreach ($arrays as $item) {
547            $results[$item] = (trim((string)$results[$item])) ? explode(',', $results[$item]) : array();
548        }
549
550        foreach ($results as $key => $data) {
551            if (!is_array($data)) {
552                if (strcasecmp((string)$data, "true") == "0") {
553                    $results[$key] = 1;
554                }
555                if (strcasecmp((string)$data, "false") == "0") {
556                    $results[$key] = 0;
557                }
558            }
559        }
560
561        return $results;
562    } // fix_preferences
563
564    /**
565     * set_defaults
566     * Make sure the default prefs are set!
567     */
568    public static function set_defaults()
569    {
570        $sql = "INSERT IGNORE INTO `preference` (`id`, `name`, `value`, `description`, `level`, `type`, `catagory`, `subcatagory`) VALUES " .
571               "(1, 'download', '1', 'Allow Downloads', 100, 'boolean', 'options', 'feature'), " .
572               "(4, 'popular_threshold', '10', 'Popular Threshold', 25, 'integer', 'interface', 'query'), " .
573               "(19, 'transcode_bitrate', '128', 'Transcode Bitrate', 25, 'string', 'streaming', 'transcoding'), " .
574               "(22, 'site_title', 'Ampache :: For the Love of Music', 'Website Title', 100, 'string', 'interface', 'custom'), " .
575               "(23, 'lock_songs', '0', 'Lock Songs', 100, 'boolean', 'system', null), " .
576               "(24, 'force_http_play', '0', 'Force HTTP playback regardless of port', 100, 'boolean', 'system', null), " .
577               "(29, 'play_type', 'web_player', 'Playback Type', 25, 'special', 'streaming', null), " .
578               "(31, 'lang', 'en_US', 'Language', 100, 'special', 'interface', null), " .
579               "(32, 'playlist_type', 'm3u', 'Playlist Type', 100, 'special', 'playlist', null), " .
580               "(33, 'theme_name', 'reborn', 'Theme', 0, 'special', 'interface', 'theme'), " .
581               "(40, 'localplay_level', '0', 'Localplay Access', 100, 'special', 'options', 'localplay'), " .
582               "(41, 'localplay_controller', '0', 'Localplay Type', 100, 'special', 'options', 'localplay'), " .
583               "(44, 'allow_stream_playback', '1', 'Allow Streaming', 100, 'boolean', 'options', 'feature'), " .
584               "(45, 'allow_democratic_playback', '0', 'Allow Democratic Play', 100, 'boolean', 'options', 'feature'), " .
585               "(46, 'allow_localplay_playback', '0', 'Allow Localplay Play', 100, 'boolean', 'options', 'localplay'), " .
586               "(47, 'stats_threshold', '7', 'Statistics Day Threshold', 25, 'integer', 'interface', 'query'), " .
587               "(51, 'offset_limit', '50', 'Offset Limit', 5, 'integer', 'interface', 'query'), " .
588               "(52, 'rate_limit', '8192', 'Rate Limit', 100, 'integer', 'streaming', 'transcoding'), " .
589               "(53, 'playlist_method', 'default', 'Playlist Method', 5, 'string', 'playlist', null), " .
590               "(55, 'transcode', 'default', 'Allow Transcoding', 25, 'string', 'streaming', 'transcoding'), " .
591               "(69, 'show_lyrics', '0', 'Show lyrics', 0, 'boolean', 'interface', 'player'), " .
592               "(70, 'mpd_active', '0', 'MPD Active Instance', 25, 'integer', 'internal', 'mpd'), " .
593               "(71, 'httpq_active', '0', 'httpQ Active Instance', 25, 'integer', 'internal', 'httpq'), " .
594               "(77, 'lastfm_grant_link', '', 'Last.FM Grant URL', 25, 'string', 'internal', 'lastfm'), " .
595               "(78, 'lastfm_challenge', '', 'Last.FM Submit Challenge', 25, 'string', 'internal', 'lastfm'), " .
596               "(82, 'now_playing_per_user', '1', 'Now Playing filtered per user', 50, 'boolean', 'interface', 'home'), " .
597               "(83, 'album_sort', '0', 'Album - Default sort', 25, 'string', 'interface', 'library'), " .
598               "(84, 'show_played_times', '0', 'Show # played', 25, 'string', 'interface', 'browse'), " .
599               "(85, 'song_page_title', '1', 'Show current song in Web player page title', 25, 'boolean', 'interface', 'player'), " .
600               "(86, 'subsonic_backend', '1', 'Use Subsonic backend', 100, 'boolean', 'system', 'backend'), " .
601               "(88, 'webplayer_flash', '1', 'Authorize Flash Web Player', 25, 'boolean', 'streaming', 'player'), " .
602               "(89, 'webplayer_html5', '1', 'Authorize HTML5 Web Player', 25, 'boolean', 'streaming', 'player'), " .
603               "(90, 'allow_personal_info_now', '1', 'Share Now Playing information', 25, 'boolean', 'interface', 'privacy'), " .
604               "(91, 'allow_personal_info_recent', '1', 'Share Recently Played information', 25, 'boolean', 'interface', 'privacy'), " .
605               "(92, 'allow_personal_info_time', '1', 'Share Recently Played information - Allow access to streaming date/time', 25, 'boolean', 'interface', 'privacy'), " .
606               "(93, 'allow_personal_info_agent', '1', 'Share Recently Played information - Allow access to streaming agent', 25, 'boolean', 'interface', 'privacy'), " .
607               "(94, 'ui_fixed', '0', 'Fix header position on compatible themes', 25, 'boolean', 'interface', 'theme'), " .
608               "(95, 'autoupdate', '1', 'Check for Ampache updates automatically', 25, 'boolean', 'system', 'update'), " .
609               "(96, 'autoupdate_lastcheck', '', 'AutoUpdate last check time', 25, 'string', 'internal', 'update'), " .
610               "(97, 'autoupdate_lastversion', '', 'AutoUpdate last version from last check', 25, 'string', 'internal', 'update'), " .
611               "(98, 'autoupdate_lastversion_new', '', 'AutoUpdate last version from last check is newer', 25, 'boolean', 'internal', 'update'), " .
612               "(99, 'webplayer_confirmclose', '0', 'Confirmation when closing current playing window', 25, 'boolean', 'interface', 'player'), " .
613               "(100, 'webplayer_pausetabs', '1', 'Auto-pause between tabs', 25, 'boolean', 'interface', 'player'), " .
614               "(101, 'stream_beautiful_url', '0', 'Enable URL Rewriting', 100, 'boolean', 'streaming', null), " .
615               "(102, 'share', '0', 'Allow Share', 100, 'boolean', 'options', 'feature'), " .
616               "(103, 'share_expire', '7', 'Share links default expiration days (0=never)', 100, 'integer', 'system', 'share'), " .
617               "(104, 'slideshow_time', '0', 'Artist slideshow inactivity time', 25, 'integer', 'interface', 'player'), " .
618               "(105, 'broadcast_by_default', '0', 'Broadcast web player by default', 25, 'boolean', 'streaming', 'player'), " .
619               "(108, 'album_group', '1', 'Album - Group multiple disks', 25, 'boolean', 'interface', 'library'), " .
620               "(109, 'topmenu', '0', 'Top menu', 25, 'boolean', 'interface', 'theme'), " .
621               "(110, 'demo_clear_sessions', '0', 'Democratic - Clear votes for expired user sessions', 25, 'boolean', 'playlist', null), " .
622               "(111, 'show_donate', '1', 'Show donate button in footer', 25, 'boolean', 'interface', null), " .
623               "(112, 'upload_catalog', '-1', 'Destination catalog', 75, 'integer', 'system', 'upload'), " .
624               "(113, 'allow_upload', '0', 'Allow user uploads', 75, 'boolean', 'system', 'upload'), " .
625               "(114, 'upload_subdir', '1', 'Create a subdirectory per user', 75, 'boolean', 'system', 'upload'), " .
626               "(115, 'upload_user_artist', '0', 'Consider the user sender as the track''s artist', 75, 'boolean', 'system', 'upload'), " .
627               "(116, 'upload_script', '', 'Post-upload script (current directory = upload target directory)', 75, 'string', 'system', 'upload'), " .
628               "(117, 'upload_allow_edit', '1', 'Allow users to edit uploaded songs', 75, 'boolean', 'system', 'upload'), " .
629               "(118, 'daap_backend', '0', 'Use DAAP backend', 100, 'boolean', 'system', 'backend'), " .
630               "(119, 'daap_pass', '', 'DAAP backend password', 100, 'string', 'system', 'backend'), " .
631               "(120, 'upnp_backend', '0', 'Use UPnP backend', 100, 'boolean', 'system', 'backend'), " .
632               "(121, 'allow_video', '0', 'Allow Video Features', 75, 'integer', 'options', 'feature'), " .
633               "(122, 'album_release_type', '1', 'Album - Group per release type', 25, 'boolean', 'interface', 'library'), " .
634               "(123, 'ajax_load', '1', 'Ajax page load', 25, 'boolean', 'interface', null), " .
635               "(124, 'direct_play_limit', '0', 'Limit direct play to maximum media count', 25, 'integer', 'interface', 'player'), " .
636               "(125, 'home_moment_albums', '1', 'Show Albums of the Moment', 25, 'integer', 'interface', 'home'), " .
637               "(126, 'home_moment_videos', '0', 'Show Videos of the Moment', 25, 'integer', 'interface', 'home'), " .
638               "(127, 'home_recently_played', '1', 'Show Recently Played', 25, 'integer', 'interface', 'home'), " .
639               "(128, 'home_now_playing', '1', 'Show Now Playing', 25, 'integer', 'interface', 'home'), " .
640               "(129, 'custom_logo', '', 'Custom URL - Logo', 25, 'string', 'interface', 'custom'), " .
641               "(130, 'album_release_type_sort', 'album,ep,live,single', 'Album - Group per release type sort', 25, 'string', 'interface', 'library'), " .
642               "(131, 'browser_notify', '1', 'Web Player browser notifications', 25, 'integer', 'interface', 'notification'), " .
643               "(132, 'browser_notify_timeout', '10', 'Web Player browser notifications timeout (seconds)', 25, 'integer', 'interface', 'notification'), " .
644               "(133, 'geolocation', '0', 'Allow Geolocation', 25, 'integer', 'options', 'feature'), " .
645               "(134, 'webplayer_aurora', '1', 'Authorize JavaScript decoder (Aurora.js) in Web Player', 25, 'boolean', 'streaming', 'player'), " .
646               "(135, 'upload_allow_remove', '1', 'Allow users to remove uploaded songs', 75, 'boolean', 'system', 'upload'), " .
647               "(136, 'custom_login_logo', '', 'Custom URL - Login page logo', 75, 'string', 'interface', 'custom'), " .
648               "(137, 'custom_favicon', '', 'Custom URL - Favicon', 75, 'string', 'interface', 'custom'), " .
649               "(138, 'custom_text_footer', '', 'Custom text footer', 75, 'string', 'interface', 'custom'), " .
650               "(139, 'webdav_backend', '0', 'Use WebDAV backend', 100, 'boolean', 'system', 'backend'), " .
651               "(140, 'notify_email', '0', 'Allow E-mail notifications', 25, 'boolean', 'options', null), " .
652               "(141, 'theme_color', 'dark', 'Theme color', 0, 'special', 'interface', 'theme'), " .
653               "(142, 'disabled_custom_metadata_fields', '', 'Custom metadata - Disable these fields', 100, 'string', 'system', 'metadata'), " .
654               "(143, 'disabled_custom_metadata_fields_input', '', 'Custom metadata - Define field list', 100, 'string', 'system', 'metadata'), " .
655               "(144, 'podcast_keep', '0', '# latest episodes to keep', 100, 'integer', 'system', 'podcast'), " .
656               "(145, 'podcast_new_download', '0', '# episodes to download when new episodes are available', 100, 'integer', 'system', 'podcast'), " .
657               "(146, 'libitem_contextmenu', '1', 'Library item context menu', 0, 'boolean', 'interface', 'library'), " .
658               "(147, 'upload_catalog_pattern', '0', 'Rename uploaded file according to catalog pattern', 100, 'boolean', 'system', 'upload'), " .
659               "(148, 'catalog_check_duplicate', '0', 'Check library item at import time and don\'t import duplicates', 100, 'boolean', 'system', 'catalog'), " .
660               "(149, 'browse_filter', '0', 'Show filter box on browse', 25, 'boolean', 'interface', 'browse'), " .
661               "(150, 'sidebar_light', '0', 'Light sidebar by default', 25, 'boolean', 'interface', 'theme'), " .
662               "(151, 'custom_blankalbum', '', 'Custom blank album default image', 75, 'string', 'interface', 'custom'), " .
663               "(152, 'custom_blankmovie', '', 'Custom blank video default image', 75, 'string', 'interface', 'custom'), " .
664               "(153, 'libitem_browse_alpha', '', 'Alphabet browsing by default for following library items (album,artist,...)', 75, 'string', 'interface', 'browse'), " .
665               "(154, 'show_skipped_times', '0', 'Show # skipped', 25, 'boolean', 'interface', 'browse'), " .
666               "(155, 'custom_datetime', '', 'Custom datetime', 25, 'string', 'interface', 'custom'), " .
667               "(156, 'cron_cache', '0', 'Cache computed SQL data (eg. media hits stats) using a cron', 25, 'boolean', 'system', 'catalog'), " .
668               "(157, 'unique_playlist', '0', 'Only add unique items to playlists', 25, 'boolean', 'playlist', NULL), " .
669               "(158, 'show_license', '0', 'Show License', 25, 'boolean', 'interface', 'browse'), " .
670               "(159, 'use_original_year', '0', 'Browse by Original Year for albums (falls back to Year)', 25, 'boolean', 'interface', 'browse'), " .
671               "(160, 'hide_single_artist', '0', 'Hide the Song Artist column for Albums with one Artist', 25, 'boolean', 'interface', 'browse');";
672        Dba::write($sql);
673    } // set_defaults
674
675    /**
676     * load_from_session
677     * This loads the preferences from the session rather then creating a connection to the database
678     * @param integer $uid
679     * @return boolean
680     */
681    public static function load_from_session($uid = -1)
682    {
683        if (isset($_SESSION['userdata']['preferences']) && is_array($_SESSION['userdata']['preferences']) && $_SESSION['userdata']['uid'] == $uid) {
684            AmpConfig::set_by_array($_SESSION['userdata']['preferences'], true);
685
686            return true;
687        }
688
689        return false;
690    } // load_from_session
691
692    /**
693     * clear_from_session
694     * This clears the users preferences, this is done whenever modifications are made to the preferences
695     * or the admin resets something
696     */
697    public static function clear_from_session()
698    {
699        unset($_SESSION['userdata']['preferences']);
700    } // clear_from_session
701
702    /**
703     * is_boolean
704     * This returns true / false if the preference in question is a boolean preference
705     * This is currently only used by the debug view, could be used other places.. wouldn't be a half
706     * bad idea
707     * @param $key
708     * @return boolean
709     */
710    public static function is_boolean($key)
711    {
712        $boolean_array = array(
713            'session_cookiesecure',
714            'require_session',
715            'access_control',
716            'require_localnet_session',
717            'downsample_remote',
718            'track_user_ip',
719            'xml_rpc',
720            'allow_zip_download',
721            'ratings',
722            'shoutbox',
723            'resize_images',
724            'show_played_times',
725            'show_skipped_times',
726            'show_album_art',
727            'allow_public_registration',
728            'captcha_public_reg',
729            'admin_notify_reg',
730            'use_rss',
731            'download',
732            'force_http_play',
733            'cookie_secure',
734            'allow_stream_playback',
735            'allow_democratic_playback',
736            'use_auth',
737            'allow_localplay_playback',
738            'debug',
739            'lock_songs',
740            'transcode_m4a',
741            'transcode_mp3',
742            'transcode_ogg',
743            'transcode_flac',
744            'httpq_active',
745            'show_lyrics'
746        );
747
748        if (in_array($key, $boolean_array)) {
749            return true;
750        }
751
752        return false;
753    } // is_boolean
754
755    /**
756     * init
757     * This grabs the preferences and then loads them into conf it should be run on page load
758     * to initialize the needed variables
759     * @return boolean
760     */
761    public static function init()
762    {
763        $user_id = Core::get_global('user')->id ? (int)(Core::get_global('user')->id) : -1;
764
765        // First go ahead and try to load it from the preferences
766        if (self::load_from_session($user_id)) {
767            return true;
768        }
769
770        /* Get Global Preferences */
771        $sql        = "SELECT `preference`.`name`, `user_preference`.`value`, `syspref`.`value` AS `system_value` FROM `preference` LEFT JOIN `user_preference` `syspref` ON `syspref`.`preference`=`preference`.`id` AND `syspref`.`user`='-1' AND `preference`.`catagory`='system' LEFT JOIN `user_preference` ON `user_preference`.`preference`=`preference`.`id` AND `user_preference`.`user` = ? AND `preference`.`catagory`!='system'";
772        $db_results = Dba::read($sql, array($user_id));
773
774        $results = array();
775        while ($row = Dba::fetch_assoc($db_results)) {
776            $value          = $row['system_value'] ? $row['system_value'] : $row['value'];
777            $name           = $row['name'];
778            $results[$name] = $value;
779        } // end while sys prefs
780
781        /* Set the Theme mojo */
782        if (strlen((string)$results['theme_name']) > 0) {
783            // In case the theme was removed
784            if (!Core::is_readable(__DIR__ . '/../../public/themes/' . $results['theme_name'])) {
785                unset($results['theme_name']);
786            }
787        } else {
788            unset($results['theme_name']);
789        }
790        // Default theme if we don't get anything from their
791        // preferences because we're going to want at least something otherwise
792        // the page is going to be really ugly
793        if (!isset($results['theme_name'])) {
794            $results['theme_name'] = 'reborn';
795        }
796        $results['theme_path'] = '/themes/' . $results['theme_name'];
797
798        // Load theme settings
799        $themecfg                  = get_theme($results['theme_name']);
800        $results['theme_css_base'] = $themecfg['base'];
801
802        if (strlen((string)$results['theme_color']) > 0) {
803            // In case the color was removed
804            if (!Core::is_readable(__DIR__ . '/../../../public/themes/' . $results['theme_name'] . '/templates/' . $results['theme_color'] . '.css')) {
805                unset($results['theme_color']);
806            }
807        } else {
808            unset($results['theme_color']);
809        }
810        if (!isset($results['theme_color'])) {
811            $results['theme_color'] = strtolower((string)$themecfg['colors'][0]);
812        }
813
814        AmpConfig::set_by_array($results, true);
815        $_SESSION['userdata']['preferences'] = $results;
816        $_SESSION['userdata']['uid']         = $user_id;
817
818        return true;
819    } // init
820}
821