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\Config\AmpConfig; 28use Ampache\Module\Authorization\AccessLevelEnum; 29use Ampache\Module\Statistics\Stats; 30use Ampache\Module\System\AmpError; 31use Ampache\Module\System\Core; 32use Ampache\Module\System\Dba; 33use Ampache\Module\Util\Ui; 34use Ampache\Repository\IpHistoryRepositoryInterface; 35use Ampache\Repository\UserRepositoryInterface; 36use Exception; 37use PDOStatement; 38 39/** 40 * This class handles all of the user related functions including the creation 41 * and deletion of the user objects from the database by default you construct it 42 * with a user_id from user.id 43 */ 44class User extends database_object 45{ 46 protected const DB_TABLENAME = 'user'; 47 48 // Basic Components 49 /** 50 * @var integer $id 51 */ 52 public $id; 53 /** 54 * @var string $username 55 */ 56 public $username; 57 /** 58 * @var string $fullname 59 */ 60 public $fullname; 61 /** 62 * @var boolean $fullname_public 63 */ 64 public $fullname_public; 65 /** 66 * @var integer $access 67 */ 68 public $access; 69 /** 70 * @var boolean $disabled 71 */ 72 public $disabled; 73 /** 74 * @var string $email 75 */ 76 public $email; 77 /** 78 * @var integer $last_seen 79 */ 80 public $last_seen; 81 /** 82 * @var integer $create_date 83 */ 84 public $create_date; 85 /** 86 * @var string $validation 87 */ 88 public $validation; 89 /** 90 * @var string $website 91 */ 92 public $website; 93 /** 94 * @var string $state 95 */ 96 public $state; 97 /** 98 * @var string $city 99 */ 100 public $city; 101 /** 102 * @var string $apikey 103 */ 104 public $apikey; 105 /** 106 * @var string $rsstoken 107 */ 108 public $rsstoken; 109 110 // Constructed variables 111 /** 112 * @var array $prefs 113 */ 114 public $prefs = array(); 115 116 /** 117 * @var Tmp_Playlist $playlist 118 */ 119 public $playlist; 120 121 /** 122 * @var string $f_name 123 */ 124 public $f_name; 125 /** 126 * @var string $f_last_seen 127 */ 128 public $f_last_seen; 129 /** 130 * @var string $f_create_date 131 */ 132 public $f_create_date; 133 /** 134 * @var string $link 135 */ 136 public $link; 137 /** 138 * @var string $f_link 139 */ 140 public $f_link; 141 /** 142 * @var string $f_usage 143 */ 144 public $f_usage; 145 /** 146 * @var string $ip_history 147 */ 148 public $ip_history; 149 /** 150 * @var string $f_avatar 151 */ 152 public $f_avatar; 153 /** 154 * @var string $f_avatar_mini 155 */ 156 public $f_avatar_mini; 157 /** 158 * @var string $f_avatar_medium 159 */ 160 public $f_avatar_medium; 161 162 /** 163 * Constructor 164 * This function is the constructor object for the user 165 * class, it currently takes a username 166 * @param integer $user_id 167 */ 168 public function __construct($user_id = 0) 169 { 170 if (!$user_id) { 171 return false; 172 } 173 174 $this->id = (int)($user_id); 175 176 $info = $this->has_info(); 177 178 foreach ($info as $key => $value) { 179 // Let's not save the password in this object :S 180 if ($key == 'password') { 181 continue; 182 } 183 $this->$key = $value; 184 } 185 186 // Make sure the Full name is always filled 187 if (strlen((string)$this->fullname) < 1) { 188 $this->fullname = $this->username; 189 } 190 191 return true; 192 } // Constructor 193 194 public function getId(): int 195 { 196 return (int) $this->id; 197 } 198 199 /** 200 * count 201 * 202 * This returns the number of user accounts that exist. 203 */ 204 public static function count() 205 { 206 $sql = 'SELECT COUNT(`id`) FROM `user`'; 207 $db_results = Dba::read($sql); 208 $data = Dba::fetch_row($db_results); 209 $results = array(); 210 $results['users'] = $data[0]; 211 212 $time = time(); 213 $last_seen = $time - 1200; 214 $sql = "SELECT COUNT(DISTINCT `session`.`username`) FROM `session` INNER JOIN `user` ON `session`.`username` = `user`.`username` WHERE `session`.`expire` > ? AND `user`.`last_seen` > ?"; 215 $db_results = Dba::read($sql, array($time, $last_seen)); 216 $data = Dba::fetch_row($db_results); 217 $results['connected'] = $data[0]; 218 219 return $results; 220 } 221 222 /** 223 * has_info 224 * This function returns the information for this object 225 * @return array 226 */ 227 private function has_info() 228 { 229 $user_id = (int)($this->id); 230 231 if (User::is_cached('user', $user_id)) { 232 return User::get_from_cache('user', $user_id); 233 } 234 235 $data = array(); 236 // If the ID is -1 then 237 if ($user_id == '-1') { 238 $data['username'] = 'System'; 239 $data['fullname'] = 'Ampache User'; 240 $data['access'] = '25'; 241 242 return $data; 243 } 244 245 $sql = "SELECT * FROM `user` WHERE `id`='$user_id'"; 246 $db_results = Dba::read($sql); 247 248 $data = Dba::fetch_assoc($db_results); 249 250 User::add_to_cache('user', $user_id, $data); 251 252 return $data; 253 } // has_info 254 255 /** 256 * load_playlist 257 * This is called once per page load it makes sure that this session 258 * has a tmp_playlist, creating it if it doesn't, then sets $this->playlist 259 * as a tmp_playlist object that can be fiddled with later on 260 */ 261 public function load_playlist() 262 { 263 $session_id = session_id(); 264 265 $this->playlist = Tmp_Playlist::get_from_session($session_id); 266 } // load_playlist 267 268 /** 269 * get_from_username 270 * This returns a built user from a username. This is a 271 * static function so it doesn't require an instance 272 * @param string $username 273 * @return User $user 274 */ 275 public static function get_from_username($username) 276 { 277 $sql = "SELECT `id` FROM `user` WHERE `username` = ? OR `fullname` = ?"; 278 $db_results = Dba::read($sql, array($username, $username)); 279 $results = Dba::fetch_assoc($db_results); 280 281 return new User($results['id']); 282 } // get_from_username 283 284 /** 285 * get_catalogs 286 * This returns the catalogs as an array of ids that this user is allowed to access 287 * @return integer[] 288 */ 289 public function get_catalogs() 290 { 291 if (parent::is_cached('user_catalog', $this->id)) { 292 return parent::get_from_cache('user_catalog', $this->id); 293 } 294 295 $sql = "SELECT * FROM `user_catalog` WHERE `user` = ?"; 296 $db_results = Dba::read($sql, array($this->id)); 297 298 $catalogs = array(); 299 while ($row = Dba::fetch_assoc($db_results)) { 300 $catalogs[] = (int)$row['catalog']; 301 } 302 303 parent::add_to_cache('user_catalog', $this->id, $catalogs); 304 305 return $catalogs; 306 } // get_catalogs 307 308 /** 309 * get_preferences 310 * This is a little more complicate now that we've got many types of preferences 311 * This function pulls all of them an arranges them into a spiffy little array 312 * You can specify a type to limit it to a single type of preference 313 * []['title'] = uppercase type name 314 * []['prefs'] = array(array('name', 'display', 'value')); 315 * []['admin'] = t/f value if this is an admin only section 316 * @param integer $type 317 * @param boolean $system 318 * @return array 319 */ 320 public function get_preferences($type = 0, $system = false) 321 { 322 // Fill out the user id 323 $user_id = $system ? Dba::escape(-1) : Dba::escape($this->id); 324 325 $user_limit = ""; 326 if (!$system) { 327 $user_limit = "AND preference.catagory != 'system'"; 328 } else { 329 if ($type != '0') { 330 $user_limit = "AND preference.catagory = '" . Dba::escape($type) . "'"; 331 } 332 } 333 334 $sql = "SELECT `preference`.`name`, `preference`.`description`, `preference`.`catagory`, `preference`.`subcatagory`, preference.level, user_preference.value FROM `preference` INNER JOIN `user_preference` ON `user_preference`.`preference` = `preference`.`id` WHERE `user_preference`.`user` = '$user_id' " . $user_limit . " ORDER BY `preference`.`catagory`, `preference`.`subcatagory`, `preference`.`description`"; 335 336 $db_results = Dba::read($sql); 337 $results = array(); 338 $type_array = array(); 339 /* Ok this is crappy, need to clean this up or improve the code FIXME */ 340 while ($row = Dba::fetch_assoc($db_results)) { 341 $type = $row['catagory']; 342 $admin = false; 343 if ($type == 'system') { 344 $admin = true; 345 } 346 $type_array[$type][$row['name']] = array( 347 'name' => $row['name'], 348 'level' => $row['level'], 349 'description' => $row['description'], 350 'value' => $row['value'], 351 'subcategory' => $row['subcatagory'] 352 ); 353 $results[$type] = array( 354 'title' => ucwords((string)$type), 355 'admin' => $admin, 356 'prefs' => $type_array[$type] 357 ); 358 } // end while 359 360 return $results; 361 } // get_preferences 362 363 /** 364 * set_preferences 365 * sets the prefs for this specific user 366 */ 367 public function set_preferences() 368 { 369 $user_id = Dba::escape($this->id); 370 $sql = "SELECT `preference`.`name`, `user_preference`.`value` FROM `preference`, `user_preference` WHERE `user_preference`.`user` = ? AND `user_preference`.`preference` = `preference`.`id` AND `preference`.`type` != 'system';"; 371 $db_results = Dba::read($sql, array($user_id)); 372 373 while ($row = Dba::fetch_assoc($db_results)) { 374 $key = $row['name']; 375 $this->prefs[$key] = $row['value']; 376 } 377 } // set_preferences 378 379 /** 380 * get_favorites 381 * returns an array of your $type favorites 382 * @param string $type 383 * @return array 384 */ 385 public function get_favorites($type) 386 { 387 $count = AmpConfig::get('popular_threshold', 10); 388 $results = Stats::get_user($count, $type, $this->id, 1); 389 390 $items = array(); 391 392 foreach ($results as $row) { 393 // If its a song 394 if ($type == 'song') { 395 $data = new Song($row['object_id']); 396 $data->count = $row['count']; 397 $data->format(); 398 $items[] = $data; 399 } elseif ($type == 'album') { 400 // If its an album 401 $data = new Album($row['object_id']); 402 $data->format(); 403 $items[] = $data; 404 } elseif ($type == 'artist') { 405 // If its an artist 406 $data = new Artist($row['object_id']); 407 $data->format(); 408 $data->f_name = $data->f_link; 409 $items[] = $data; 410 } elseif (($type == 'genre' || $type == 'tag')) { 411 // If it's a genre 412 $data = new Tag($row['object_id']); 413 $items[] = $data; 414 } 415 } // end foreach 416 417 return $items; 418 } // get_favorites 419 420 /** 421 * is_logged_in 422 * checks to see if $this user is logged in returns their current IP if they 423 * are logged in 424 */ 425 public function is_logged_in() 426 { 427 $username = Dba::escape($this->username); 428 429 $sql = "SELECT `id`, `ip` FROM `session` WHERE `username`='$username' AND `expire` > " . time(); 430 $db_results = Dba::read($sql); 431 432 if ($row = Dba::fetch_assoc($db_results)) { 433 return $row['ip'] ? $row['ip'] : null; 434 } 435 436 return false; 437 } // is_logged_in 438 439 /** 440 * has_access 441 * this function checks to see if this user has access 442 * to the passed action (pass a level requirement) 443 * @param integer $needed_level 444 * @return boolean 445 */ 446 public function has_access($needed_level) 447 { 448 if (AmpConfig::get('demo_mode')) { 449 return true; 450 } 451 452 if ($this->access >= $needed_level) { 453 return true; 454 } 455 456 return false; 457 } // has_access 458 459 /** 460 * is_registered 461 * Check if the user is registered 462 * @return boolean 463 */ 464 public static function is_registered() 465 { 466 if (!Core::get_global('user')->id) { 467 return false; 468 } 469 470 if (!AmpConfig::get('use_auth') && Core::get_global('user')->access < 5) { 471 return false; 472 } 473 474 return true; 475 } 476 477 /** 478 * set_user_data 479 * This updates some background data for user specific function 480 * @param string $key 481 * @param string|integer $value 482 */ 483 public static function set_user_data($user_id, $key, $value) 484 { 485 Dba::write("REPLACE INTO `user_data` SET `user`= ?, `key`= ?, `value`= ?;", array($user_id, $key, $value)); 486 } // set_user_data 487 488 /** 489 * get_user_data 490 * This updates some background data for user specific function 491 * @param string $user_id 492 * @param string $key 493 * @return array 494 */ 495 public static function get_user_data($user_id, $key = null) 496 { 497 $sql = "SELECT `key`, `value` FROM `user_data` WHERE `user` = ?"; 498 $params = array($user_id); 499 if ($key) { 500 $sql .= " AND `key` = ?"; 501 $params[] = $key; 502 } 503 504 $db_results = Dba::read($sql, $params); 505 $results = array(); 506 while ($row = Dba::fetch_assoc($db_results)) { 507 $results[$row['key']] = $row['value']; 508 } 509 510 return $results; 511 } // get_user_data 512 513 /** 514 * update 515 * This function is an all encompassing update function that 516 * calls the mini ones does all the error checking and all that 517 * good stuff 518 * @param array $data 519 * @return boolean|int 520 */ 521 public function update(array $data) 522 { 523 if (empty($data['username'])) { 524 AmpError::add('username', T_('Username is required')); 525 } 526 527 if ($data['password1'] != $data['password2'] && !empty($data['password1'])) { 528 AmpError::add('password', T_("Passwords do not match")); 529 } 530 531 if (AmpError::occurred()) { 532 return false; 533 } 534 535 if (!isset($data['fullname_public'])) { 536 $data['fullname_public'] = false; 537 } 538 539 foreach ($data as $name => $value) { 540 if ($name == 'password1') { 541 $name = 'password'; 542 } else { 543 $value = scrub_in($value); 544 } 545 546 switch ($name) { 547 case 'password': 548 case 'access': 549 case 'email': 550 case 'username': 551 case 'fullname': 552 case 'fullname_public': 553 case 'website': 554 case 'state': 555 case 'city': 556 if ($this->$name != $value) { 557 $function = 'update_' . $name; 558 $this->$function($value); 559 } 560 break; 561 case 'clear_stats': 562 Stats::clear($this->id); 563 break; 564 default: 565 break; 566 } 567 } 568 569 return $this->id; 570 } 571 572 /** 573 * update_username 574 * updates their username 575 * @param $new_username 576 */ 577 public function update_username($new_username) 578 { 579 $sql = "UPDATE `user` SET `username` = ? WHERE `id` = ?"; 580 $this->username = $new_username; 581 582 debug_event(self::class, 'Updating username', 4); 583 584 Dba::write($sql, array($new_username, $this->id)); 585 } // update_username 586 587 /** 588 * update_validation 589 * This is used by the registration mumbojumbo 590 * Use this function to update the validation key 591 * NOTE: crap this doesn't have update_item the humanity of it all 592 * @param $new_validation 593 * @return PDOStatement|boolean 594 */ 595 public function update_validation($new_validation) 596 { 597 $sql = "UPDATE `user` SET `validation` = ?, `disabled`='1' WHERE `id` = ?"; 598 $db_results = Dba::write($sql, array($new_validation, $this->id)); 599 $this->validation = $new_validation; 600 601 return $db_results; 602 } // update_validation 603 604 /** 605 * update_fullname 606 * updates their fullname 607 * @param $new_fullname 608 */ 609 public function update_fullname($new_fullname) 610 { 611 $sql = "UPDATE `user` SET `fullname` = ? WHERE `id` = ?"; 612 613 debug_event(self::class, 'Updating fullname', 4); 614 615 Dba::write($sql, array($new_fullname, $this->id)); 616 } // update_fullname 617 618 /** 619 * update_fullname_public 620 * updates their fullname public 621 * @param $new_fullname_public 622 */ 623 public function update_fullname_public($new_fullname_public) 624 { 625 $sql = "UPDATE `user` SET `fullname_public` = ? WHERE `id` = ?"; 626 627 debug_event(self::class, 'Updating fullname public', 4); 628 629 Dba::write($sql, array($new_fullname_public ? '1' : '0', $this->id)); 630 } // update_fullname_public 631 632 /** 633 * update_email 634 * updates their email address 635 * @param string $new_email 636 */ 637 public function update_email($new_email) 638 { 639 $sql = "UPDATE `user` SET `email` = ? WHERE `id` = ?"; 640 641 debug_event(self::class, 'Updating email', 4); 642 643 Dba::write($sql, array($new_email, $this->id)); 644 } // update_email 645 646 /** 647 * update_website 648 * updates their website address 649 * @param $new_website 650 */ 651 public function update_website($new_website) 652 { 653 $new_website = rtrim((string)$new_website, "/"); 654 $sql = "UPDATE `user` SET `website` = ? WHERE `id` = ?"; 655 656 debug_event(self::class, 'Updating website', 4); 657 658 Dba::write($sql, array($new_website, $this->id)); 659 } // update_website 660 661 /** 662 * update_state 663 * updates their state 664 * @param $new_state 665 */ 666 public function update_state($new_state) 667 { 668 $sql = "UPDATE `user` SET `state` = ? WHERE `id` = ?"; 669 670 debug_event(self::class, 'Updating state', 4); 671 672 Dba::write($sql, array($new_state, $this->id)); 673 } // update_state 674 675 /** 676 * update_city 677 * updates their city 678 * @param $new_city 679 */ 680 public function update_city($new_city) 681 { 682 $sql = "UPDATE `user` SET `city` = ? WHERE `id` = ?"; 683 684 debug_event(self::class, 'Updating city', 4); 685 686 Dba::write($sql, array($new_city, $this->id)); 687 } // update_city 688 689 /** 690 * update_counts for individual users 691 */ 692 public static function update_counts() 693 { 694 $catalog_disable = AmpConfig::get('catalog_disable'); 695 $catalog_filter = AmpConfig::get('catalog_filter'); 696 $sql = "SELECT `id` FROM `user`"; 697 $db_results = Dba::read($sql); 698 $user_list = array(); 699 while ($results = Dba::fetch_assoc($db_results)) { 700 $user_list[] = (int)$results['id']; 701 } 702 if (!$catalog_filter) { 703 // no filter means no need for filtering or counting per user 704 $count_array = array('song', 'video', 'podcast_episode', 'artist', 'album', 'search', 'playlist', 'live_stream', 'podcast', 'user', 'catalog', 'label', 'tag', 'share', 'license', 'album_group', 'items', 'time', 'size'); 705 $server_counts = Catalog::get_server_counts(0); 706 foreach ($user_list as $user_id) { 707 debug_event(self::class, 'Update counts for ' . $user_id, 5); 708 foreach ($server_counts as $table => $count) { 709 if (in_array($table, $count_array)) { 710 self::set_count($user_id, $table, $count); 711 } 712 } 713 } 714 715 return; 716 } 717 718 $count_array = array('song', 'video', 'podcast_episode', 'artist', 'album', 'search', 'playlist', 'live_stream', 'podcast', 'user', 'catalog', 'label', 'tag', 'share', 'license'); 719 foreach ($user_list as $user_id) { 720 debug_event(self::class, 'Update counts for ' . $user_id, 5); 721 // get counts per user (filtered catalogs aren't counted) 722 foreach ($count_array as $table) { 723 $sql = (in_array($table, array('search', 'user', 'license'))) 724 ? "SELECT COUNT(`id`) FROM `$table`" 725 : "SELECT COUNT(`id`) FROM `$table` WHERE" . Catalog::get_user_filter($table, $user_id); 726 $db_results = Dba::read($sql); 727 $data = Dba::fetch_row($db_results); 728 729 self::set_count($user_id, $table, (int)$data[0]); 730 } 731 // tables with media items to count, song-related tables and the rest 732 $media_tables = array('song', 'video', 'podcast_episode'); 733 $items = 0; 734 $time = 0; 735 $size = 0; 736 foreach ($media_tables as $table) { 737 $enabled_sql = ($catalog_disable && $table !== 'podcast_episode') 738 ? " WHERE `$table`.`enabled`='1' AND" 739 : ' WHERE'; 740 $sql = "SELECT COUNT(`id`), IFNULL(SUM(`time`), 0), IFNULL(SUM(`size`), 0) FROM `$table`" . $enabled_sql . Catalog::get_user_filter($table, $user_id); 741 $db_results = Dba::read($sql); 742 $data = Dba::fetch_row($db_results); 743 // save the object and add to the current size 744 $items += (int)$data[0]; 745 $time += (int)$data[1]; 746 $size += (int)$data[2]; 747 self::set_count($user_id, $table, (int)$data[0]); 748 } 749 self::set_count($user_id, 'items', $items); 750 self::set_count($user_id, 'time', $time); 751 self::set_count($user_id, 'size', $size); 752 // grouped album counts 753 $sql = "SELECT COUNT(DISTINCT(`album`.`id`)) AS `count` FROM `album` WHERE `id` in (SELECT MIN(`id`) from `album` GROUP BY `album`.`prefix`, `album`.`name`, `album`.`album_artist`, `album`.`release_type`, `album`.`release_status`, `album`.`mbid`, `album`.`year`, `album`.`original_year`) AND" . Catalog::get_user_filter('album', $user_id); 754 $db_results = Dba::read($sql); 755 $data = Dba::fetch_row($db_results); 756 self::set_count($user_id, 'album_group', (int)$data[0]); 757 } 758 } // update_counts 759 760 /** 761 * set_count 762 * 763 * write the total_counts to update_info 764 * @param int $user_id 765 * @param string $key 766 * @param int $value 767 */ 768 public static function set_count(int $user_id, string $key, int $value) 769 { 770 Dba::write("REPLACE INTO `user_data` SET `user` = ?, `key`= ?, `value`=?;", array($user_id, $key, $value)); 771 } // set_count 772 773 /** 774 * disable 775 * This disables the current user 776 */ 777 public function disable() 778 { 779 // Make sure we aren't disabling the last admin 780 $sql = "SELECT `id` FROM `user` WHERE `disabled` = '0' AND `id` != '" . $this->id . "' AND `access`='100'"; 781 $db_results = Dba::read($sql); 782 783 if (!Dba::num_rows($db_results)) { 784 return false; 785 } 786 787 $sql = "UPDATE `user` SET `disabled`='1' WHERE id='" . $this->id . "'"; 788 Dba::write($sql); 789 790 // Delete any sessions they may have 791 $sql = "DELETE FROM `session` WHERE `username`='" . Dba::escape($this->username) . "'"; 792 Dba::write($sql); 793 794 return true; 795 } // disable 796 797 /** 798 * update_access 799 * updates their access level 800 * @param $new_access 801 * @return boolean 802 */ 803 public function update_access($new_access) 804 { 805 /* Prevent Only User accounts */ 806 if ($new_access < '100') { 807 $sql = "SELECT `id` FROM `user` WHERE `access`='100' AND `id` != '$this->id'"; 808 $db_results = Dba::read($sql); 809 if (!Dba::num_rows($db_results)) { 810 return false; 811 } 812 } 813 814 $new_access = Dba::escape($new_access); 815 $sql = "UPDATE `user` SET `access`='$new_access' WHERE `id`='$this->id'"; 816 817 debug_event(self::class, 'Updating access level for ' . $this->id, 4); 818 819 Dba::write($sql); 820 821 return true; 822 } // update_access 823 824 /** 825 * save_mediaplay 826 * @param User $user 827 * @param Song $media 828 */ 829 public static function save_mediaplay($user, $media) 830 { 831 foreach (Plugin::get_plugins('save_mediaplay') as $plugin_name) { 832 try { 833 $plugin = new Plugin($plugin_name); 834 if ($plugin->load($user)) { 835 debug_event(self::class, 'save_mediaplay... ' . $plugin->_plugin->name, 5); 836 $plugin->_plugin->save_mediaplay($media); 837 } 838 } catch (Exception $error) { 839 debug_event(self::class, 'save_mediaplay plugin error: ' . $error->getMessage(), 1); 840 } 841 } 842 } 843 844 /** 845 * insert_ip_history 846 * This inserts a row into the IP History recording this user at this 847 * address at this time in this place, doing this thing.. you get the point 848 */ 849 public function insert_ip_history() 850 { 851 $sip = (filter_has_var(INPUT_SERVER, 'HTTP_X_FORWARDED_FOR')) 852 ? filter_var(Core::get_server('HTTP_X_FORWARDED_FOR'), FILTER_VALIDATE_IP) 853 : filter_var(Core::get_server('REMOTE_ADDR'), FILTER_VALIDATE_IP); 854 debug_event(self::class, 'Login from IP address: ' . (string) $sip, 3); 855 856 // Remove port information if any 857 if (!empty($sip)) { 858 // Use parse_url to support easily ipv6 859 if (filter_var($sip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === true) { 860 $sipar = parse_url("http://" . $sip); 861 } else { 862 $sipar = parse_url("http://[" . $sip . "]"); 863 } 864 $sip = $sipar['host']; 865 } 866 867 $uip = (!empty($sip)) ? Dba::escape(inet_pton(trim((string)$sip, "[]"))) : ''; 868 $date = time(); 869 $user_id = (int)$this->id; 870 $agent = Dba::escape(Core::get_server('HTTP_USER_AGENT')); 871 872 $sql = "INSERT INTO `ip_history` (`ip`, `user`, `date`, `agent`) VALUES ('$uip', '$user_id', '$date', '$agent')"; 873 Dba::write($sql); 874 875 /* Clean up old records... sometimes */ 876 if (rand(1, 100) > 60) { 877 $date = time() - (86400 * AmpConfig::get('user_ip_cardinality')); 878 $sql = "DELETE FROM `ip_history` WHERE `date` < $date"; 879 Dba::write($sql); 880 } 881 882 return true; 883 } // insert_ip_history 884 885 /** 886 * create 887 * inserts a new user into Ampache 888 * @param string $username 889 * @param string $fullname 890 * @param string $email 891 * @param string $website 892 * @param string $password 893 * @param integer $access 894 * @param string $state 895 * @param string $city 896 * @param boolean $disabled 897 * @param boolean $encrypted 898 * @return integer 899 */ 900 public static function create( 901 $username, 902 $fullname, 903 $email, 904 $website, 905 $password, 906 $access, 907 $state = '', 908 $city = '', 909 $disabled = false, 910 $encrypted = false 911 ) { 912 $website = rtrim((string)$website, "/"); 913 if (!$encrypted) { 914 $password = hash('sha256', $password); 915 } 916 $disabled = $disabled ? 1 : 0; 917 918 /* Now Insert this new user */ 919 $sql = "INSERT INTO `user` (`username`, `disabled`, `fullname`, `email`, `password`, `access`, `create_date`"; 920 $params = array($username, $disabled, $fullname, $email, $password, $access, time()); 921 922 if (!empty($website)) { 923 $sql .= ", `website`"; 924 $params[] = $website; 925 } 926 if (!empty($state)) { 927 $sql .= ", `state`"; 928 $params[] = $state; 929 } 930 if (!empty($city)) { 931 $sql .= ", `city`"; 932 $params[] = $city; 933 } 934 935 $sql .= ") VALUES(?, ?, ?, ?, ?, ?, ?"; 936 937 if (!empty($website)) { 938 $sql .= ", ?"; 939 } 940 if (!empty($state)) { 941 $sql .= ", ?"; 942 } 943 if (!empty($city)) { 944 $sql .= ", ?"; 945 } 946 947 $sql .= ")"; 948 $db_results = Dba::write($sql, $params); 949 950 if (!$db_results) { 951 return null; 952 } 953 954 // Get the insert_id 955 $insert_id = (int)Dba::insert_id(); 956 957 // Populates any missing preferences, in this case all of them 958 self::fix_preferences($insert_id); 959 960 return (int)$insert_id; 961 } // create 962 963 /** 964 * update_password 965 * updates a users password 966 * @param string $new_password 967 * @param string $hashed_password 968 */ 969 public function update_password($new_password, $hashed_password = null) 970 { 971 debug_event(self::class, 'Updating password', 1); 972 if (!$hashed_password) { 973 $hashed_password = hash('sha256', $new_password); 974 } 975 976 $escaped_password = Dba::escape($hashed_password); 977 $sql = "UPDATE `user` SET `password` = ? WHERE `id` = ?"; 978 $db_results = Dba::write($sql, array($escaped_password, $this->id)); 979 980 // Clear this (temp fix) 981 if ($db_results) { 982 unset($_SESSION['userdata']['password']); 983 } 984 } // update_password 985 986 /** 987 * format 988 * This function sets up the extra variables we need when we are displaying a 989 * user for an admin, these should not be normally called when creating a 990 * user object 991 * @param boolean $details 992 */ 993 public function format($details = true) 994 { 995 if (!$this->id) { 996 return; 997 } 998 /* If they have a last seen date */ 999 if (!$this->last_seen) { 1000 $this->f_last_seen = T_('Never'); 1001 } else { 1002 $this->f_last_seen = get_datetime((int)$this->last_seen); 1003 } 1004 1005 /* If they have a create date */ 1006 if (!$this->create_date) { 1007 $this->f_create_date = T_('Unknown'); 1008 } else { 1009 $this->f_create_date = get_datetime((int)$this->create_date); 1010 } 1011 1012 $this->f_name = ($this->fullname_public) 1013 ? filter_var($this->fullname, FILTER_SANITIZE_STRING, FILTER_FLAG_NO_ENCODE_QUOTES) 1014 : filter_var($this->username, FILTER_SANITIZE_STRING, FILTER_FLAG_NO_ENCODE_QUOTES); 1015 1016 // Base link 1017 $this->link = AmpConfig::get('web_path') . '/stats.php?action=show_user&user_id=' . $this->id; 1018 $this->f_link = '<a href="' . $this->link . '">' . scrub_out($this->f_name) . '</a>'; 1019 1020 if ($details) { 1021 $user_data = self::get_user_data($this->id); 1022 if (!isset($user_data['play_size'])) { 1023 // Calculate their total Bandwidth Usage 1024 $sql = "SELECT SUM(`song`.`size`) as `play_size` FROM `object_count` LEFT JOIN `song` ON `song`.`id`=`object_count`.`object_id` WHERE `object_count`.`user` = ? AND `object_count`.`object_type` IN ('song', 'video', 'podcast_episode') GROUP BY `user`;"; 1025 $db_results = Dba::read($sql, array($this->id)); 1026 $result = Dba::fetch_assoc($db_results); 1027 // set the value for next time 1028 self::set_user_data($this->id, 'play_size', (int)$result['play_size']); 1029 $user_data['play_size'] = $result['play_size']; 1030 } 1031 1032 $this->f_usage = Ui::format_bytes((int)$user_data['play_size']); 1033 1034 // Get Users Last ip 1035 if (count($data = $this->getIpHistoryRepository()->getHistory($this->getId()))) { 1036 $user_ip = inet_ntop($data['0']['ip']); 1037 $this->ip_history = (!empty($user_ip) && filter_var($user_ip, FILTER_VALIDATE_IP)) ? $user_ip : T_('Invalid'); 1038 } else { 1039 $this->ip_history = T_('Not Enough Data'); 1040 } 1041 } 1042 1043 $avatar = $this->get_avatar(); 1044 if (!empty($avatar['url'])) { 1045 $this->f_avatar = '<img src="' . $avatar['url'] . '" title="' . $avatar['title'] . '"' . ' width="256px" height="auto" />'; 1046 } 1047 if (!empty($avatar['url_mini'])) { 1048 $this->f_avatar_mini = '<img src="' . $avatar['url_mini'] . '" title="' . $avatar['title'] . '" style="width: 32px; height: 32px;" />'; 1049 } 1050 if (!empty($avatar['url_medium'])) { 1051 $this->f_avatar_medium = '<img src="' . $avatar['url_medium'] . '" title="' . $avatar['title'] . '" style="width: 64px; height: 64px;" />'; 1052 } 1053 } // format_user 1054 1055 /** 1056 * access_name_to_level 1057 * This takes the access name for the user and returns the level 1058 * @param string $name 1059 * @return integer 1060 */ 1061 public static function access_name_to_level($name) 1062 { 1063 switch ($name) { 1064 case 'admin': 1065 return AccessLevelEnum::LEVEL_ADMIN; 1066 case 'user': 1067 return AccessLevelEnum::LEVEL_USER; 1068 case 'manager': 1069 return AccessLevelEnum::LEVEL_MANAGER; 1070 // FIXME why is content manager not here? 1071 //case 'manager': 1072 //return AccessLevelEnum::LEVEL_CONTENT_MANAGER; 1073 case 'guest': 1074 return AccessLevelEnum::LEVEL_GUEST; 1075 default: 1076 return AccessLevelEnum::LEVEL_DEFAULT; 1077 } 1078 } // access_name_to_level 1079 1080 /** 1081 * access_level_to_name 1082 * This takes the access level for the user and returns the translated name for that level 1083 * @param string $level 1084 * @return string 1085 */ 1086 public static function access_level_to_name($level) 1087 { 1088 switch ($level) { 1089 case '100': 1090 return T_('Admin'); 1091 case '75': 1092 return T_('Catalog Manager'); 1093 case '50': 1094 return T_('Content Manager'); 1095 case '25': 1096 return T_('User'); 1097 case '5': 1098 return T_('Guest'); 1099 default: 1100 return T_('Unknown'); 1101 } 1102 } // access_level_to_name 1103 1104 /** 1105 * fix_preferences 1106 * This is the new fix_preferences function, it does the following 1107 * Remove Duplicates from user, add in missing 1108 * If -1 is passed it also removes duplicates from the `preferences` 1109 * table. 1110 * @param integer $user_id 1111 */ 1112 public static function fix_preferences($user_id) 1113 { 1114 $user_id = Dba::escape($user_id); 1115 1116 // Delete that system pref that's not a user pref... 1117 if ($user_id > 0) { 1118 // TODO, remove before next release. ('custom_login_logo' needs to be here a while at least so 5.0.0+1) 1119 $sql = "DELETE FROM `user_preference` WHERE `preference` IN (SELECT `id` from `preference` where `name` IN ('custom_login_background', 'custom_login_logo')) AND `user` = $user_id"; 1120 Dba::write($sql); 1121 } 1122 1123 /* Get All Preferences for the current user */ 1124 $sql = "SELECT * FROM `user_preference` WHERE `user`='$user_id'"; 1125 $db_results = Dba::read($sql); 1126 1127 $results = array(); 1128 $zero_results = array(); 1129 1130 while ($row = Dba::fetch_assoc($db_results)) { 1131 $pref_id = $row['preference']; 1132 /* Check for duplicates */ 1133 if (isset($results[$pref_id])) { 1134 $row['value'] = Dba::escape($row['value']); 1135 $sql = "DELETE FROM `user_preference` WHERE `user`='$user_id' AND `preference`='" . $row['preference'] . "' AND `value`='" . Dba::escape($row['value']) . "'"; 1136 Dba::write($sql); 1137 } else { 1138 // if its set 1139 $results[$pref_id] = 1; 1140 } 1141 } // end while 1142 1143 /* If we aren't the -1 user before we continue grab the -1 users values */ 1144 if ($user_id != '-1') { 1145 $sql = "SELECT `user_preference`.`preference`, `user_preference`.`value` FROM `user_preference`, `preference` WHERE `user_preference`.`preference` = `preference`.`id` AND `user_preference`.`user`='-1' AND `preference`.`catagory` !='system' AND `preference`.`name` NOT IN ('custom_login_background', 'custom_login_logo') "; 1146 $db_results = Dba::read($sql); 1147 /* While through our base stuff */ 1148 while ($row = Dba::fetch_assoc($db_results)) { 1149 $key = $row['preference']; 1150 $zero_results[$key] = $row['value']; 1151 } 1152 } // if not user -1 1153 1154 // get me _EVERYTHING_ 1155 $sql = "SELECT * FROM `preference`"; 1156 1157 // If not system, exclude system... *gasp* 1158 if ($user_id != '-1') { 1159 $sql .= " WHERE catagory !='system'"; 1160 $sql .= " AND `preference`.`name` NOT IN ('custom_login_background', 'custom_login_logo')"; 1161 } 1162 $db_results = Dba::read($sql); 1163 1164 while ($row = Dba::fetch_assoc($db_results)) { 1165 $key = $row['id']; 1166 1167 /* Check if this preference is set */ 1168 if (!isset($results[$key])) { 1169 if (isset($zero_results[$key])) { 1170 $row['value'] = $zero_results[$key]; 1171 } 1172 $value = Dba::escape($row['value']); 1173 $sql = "INSERT INTO user_preference (`user`, `preference`, `value`) VALUES ('$user_id', '$key', '$value')"; 1174 Dba::write($sql); 1175 } 1176 } // while preferences 1177 } // fix_preferences 1178 1179 /** 1180 * delete 1181 * deletes this user and everything associated with it. This will affect 1182 * ratings and total stats 1183 * @return boolean 1184 */ 1185 public function delete() 1186 { 1187 // Before we do anything make sure that they aren't the last admin 1188 if ($this->has_access(100)) { 1189 $sql = "SELECT `id` FROM `user` WHERE `access`='100' AND id != ?"; 1190 $db_results = Dba::read($sql, array($this->id)); 1191 if (!Dba::num_rows($db_results)) { 1192 return false; 1193 } 1194 } // if this is an admin check for others 1195 1196 // simple deletion queries. 1197 $user_tables = array( 1198 'playlist', 1199 'object_count', 1200 'ip_history', 1201 'access_list', 1202 'rating', 1203 'tag_map', 1204 'user_preference', 1205 'user_vote' 1206 ); 1207 foreach ($user_tables as $table_id) { 1208 $sql = "DELETE FROM `" . $table_id . "` WHERE `user` = ?"; 1209 Dba::write($sql, array($this->id)); 1210 } 1211 // Clean up the playlist data table 1212 $sql = "DELETE FROM `playlist_data` USING `playlist_data` LEFT JOIN `playlist` ON `playlist`.`id`=`playlist_data`.`playlist` WHERE `playlist`.`id` IS NULL"; 1213 Dba::write($sql); 1214 1215 // Clean out the tags 1216 $sql = "DELETE FROM `tag` WHERE `tag`.`id` NOT IN (SELECT `tag_id` FROM `tag_map`)"; 1217 Dba::write($sql); 1218 1219 // Delete their following/followers 1220 $sql = "DELETE FROM `user_follower` WHERE `user` = ? OR `follow_user` = ?"; 1221 Dba::write($sql, array($this->id, $this->id)); 1222 1223 // Delete the user itself 1224 $sql = "DELETE FROM `user` WHERE `id` = ?"; 1225 Dba::write($sql, array($this->id)); 1226 1227 $sql = "DELETE FROM `session` WHERE `username` = ?"; 1228 Dba::write($sql, array($this->username)); 1229 1230 return true; 1231 } // delete 1232 1233 /** 1234 * is_online 1235 * delay how long since last_seen in seconds default of 20 min 1236 * calculates difference between now and last_seen 1237 * if less than delay, we consider them still online 1238 * @param integer $delay 1239 * @return boolean 1240 */ 1241 public function is_online($delay = 1200) 1242 { 1243 return time() - $this->last_seen <= $delay; 1244 } // is_online 1245 1246 /** 1247 * get_recently_played 1248 * This gets the recently played items for this user respecting 1249 * the limit passed. ger recent by default or oldest if $newest is false. 1250 * @param string $type 1251 * @param integer $count 1252 * @param integer $offset 1253 * @param boolean $newest 1254 * @return array 1255 */ 1256 public function get_recently_played($type, $count, $offset = 0, $newest = true) 1257 { 1258 $ordersql = ($newest === true) ? 'DESC' : 'ASC'; 1259 $limit = ($offset < 1) ? $count : $offset . "," . $count; 1260 1261 $sql = "SELECT `object_id`, MAX(`date`) AS `date` FROM `object_count` WHERE `object_type` = ? AND `user` = ? GROUP BY `object_id` ORDER BY `date` " . $ordersql . " LIMIT " . $limit . " "; 1262 $db_results = Dba::read($sql, array($type, $this->id)); 1263 1264 $results = array(); 1265 while ($row = Dba::fetch_assoc($db_results)) { 1266 $results[] = $row['object_id']; 1267 } 1268 1269 return $results; 1270 } // get_recently_played 1271 1272 /** 1273 * Get item fullname. 1274 * @return string 1275 */ 1276 public function get_fullname() 1277 { 1278 return $this->f_name; 1279 } 1280 1281 /** 1282 * Get item name based on whether they allow public fullname access. 1283 * @param int $user_id 1284 * @return string 1285 */ 1286 public static function get_username($user_id) 1287 { 1288 $users = static::getUserRepository()->getValidArray(true); 1289 $username = (isset($users[$user_id])) 1290 ? $users[$user_id] 1291 : T_('System'); 1292 1293 return $username; 1294 } 1295 1296 /** 1297 * get_avatar 1298 * Get the user avatar 1299 * @param boolean $local 1300 * @param array $session 1301 * @return array 1302 */ 1303 public function get_avatar($local = false, $session = array()) 1304 { 1305 $avatar = array(); 1306 $auth = ''; 1307 if ($session['t'] && $session['s']) { 1308 $auth = '&t=' . $session['t'] . '&s=' . $session['s']; 1309 } elseif ($session['auth']) { 1310 $auth = '&auth=' . $session['auth']; 1311 } 1312 1313 $avatar['title'] = T_('User avatar'); 1314 $upavatar = new Art($this->id, 'user'); 1315 if ($upavatar->has_db_info()) { 1316 $avatar['url'] = ($local ? AmpConfig::get('local_web_path') : AmpConfig::get('web_path')) . '/image.php?object_type=user&object_id=' . $this->id . $auth; 1317 $avatar['url_mini'] = $avatar['url']; 1318 $avatar['url_medium'] = $avatar['url']; 1319 $avatar['url'] .= '&thumb=4'; 1320 $avatar['url_mini'] .= '&thumb=5'; 1321 $avatar['url_medium'] .= '&thumb=3'; 1322 } else { 1323 foreach (Plugin::get_plugins('get_avatar_url') as $plugin_name) { 1324 $plugin = new Plugin($plugin_name); 1325 if ($plugin->load(Core::get_global('user'))) { 1326 $avatar['url'] = $plugin->_plugin->get_avatar_url($this); 1327 if (!empty($avatar['url'])) { 1328 $avatar['url_mini'] = $plugin->_plugin->get_avatar_url($this, 32); 1329 $avatar['url_medium'] = $plugin->_plugin->get_avatar_url($this, 64); 1330 $avatar['title'] .= ' (' . $plugin->_plugin->name . ')'; 1331 break; 1332 } 1333 } 1334 } 1335 } 1336 1337 if ($avatar['url'] === null) { 1338 $avatar['url'] = ($local ? AmpConfig::get('local_web_path') : AmpConfig::get('web_path')) . '/images/blankuser.png'; 1339 $avatar['url_mini'] = $avatar['url']; 1340 $avatar['url_medium'] = $avatar['url']; 1341 } 1342 1343 return $avatar; 1344 } // get_avatar 1345 1346 /** 1347 * @param string $data 1348 * @param string $mime 1349 * @return boolean 1350 */ 1351 public function update_avatar($data, $mime = '') 1352 { 1353 debug_event(self::class, 'Updating avatar for ' . $this->id, 4); 1354 1355 $art = new Art($this->id, 'user'); 1356 1357 return $art->insert($data, $mime); 1358 } 1359 1360 /** 1361 * 1362 * @return boolean 1363 */ 1364 public function upload_avatar() 1365 { 1366 $upload = array(); 1367 if (!empty($_FILES['avatar']['tmp_name']) && $_FILES['avatar']['size'] <= AmpConfig::get('max_upload_size')) { 1368 $path_info = pathinfo($_FILES['avatar']['name']); 1369 $upload['file'] = $_FILES['avatar']['tmp_name']; 1370 $upload['mime'] = 'image/' . $path_info['extension']; 1371 $image_data = Art::get_from_source($upload, 'user'); 1372 1373 if ($image_data !== '') { 1374 return $this->update_avatar($image_data, $upload['mime']); 1375 } 1376 } 1377 1378 return true; // only worry about failed uploads 1379 } 1380 1381 public function delete_avatar() 1382 { 1383 $art = new Art($this->id, 'user'); 1384 $art->reset(); 1385 } 1386 1387 /** 1388 * rebuild_all_preferences 1389 * This rebuilds the user preferences for all installed users, called by the plugin functions 1390 */ 1391 public static function rebuild_all_preferences() 1392 { 1393 // Clean out any preferences garbage left over 1394 $sql = "DELETE `user_preference`.* FROM `user_preference` LEFT JOIN `user` ON `user_preference`.`user` = `user`.`id` WHERE `user_preference`.`user` != -1 AND `user`.`id` IS NULL"; 1395 Dba::write($sql); 1396 1397 // Get only users who has less preferences than excepted 1398 // otherwise it would have significant performance issue with large user database 1399 $sql = "SELECT `user` FROM `user_preference` GROUP BY `user` HAVING COUNT(*) < (SELECT COUNT(`id`) FROM `preference` WHERE `catagory` != 'system')"; 1400 $db_results = Dba::read($sql); 1401 while ($row = Dba::fetch_assoc($db_results)) { 1402 self::fix_preferences($row['user']); 1403 } 1404 1405 return true; 1406 } // rebuild_all_preferences 1407 1408 /** 1409 * stream_control 1410 * Check all stream control plugins 1411 * @param array $media_ids 1412 * @param User|null $user 1413 * @return boolean 1414 */ 1415 public static function stream_control($media_ids, User $user = null) 1416 { 1417 if ($user === null) { 1418 $user = Core::get_global('user'); 1419 } 1420 1421 foreach (Plugin::get_plugins('stream_control') as $plugin_name) { 1422 $plugin = new Plugin($plugin_name); 1423 if ($plugin->load($user)) { 1424 if (!$plugin->_plugin->stream_control($media_ids)) { 1425 return false; 1426 } 1427 } 1428 } 1429 1430 return true; 1431 } 1432 1433 /** 1434 * @deprecated inject dependency 1435 */ 1436 private function getIpHistoryRepository(): IpHistoryRepositoryInterface 1437 { 1438 global $dic; 1439 1440 return $dic->get(IpHistoryRepositoryInterface::class); 1441 } 1442 1443 /** 1444 * @deprecated inject dependency 1445 */ 1446 private static function getUserRepository(): UserRepositoryInterface 1447 { 1448 global $dic; 1449 1450 return $dic->get(UserRepositoryInterface::class); 1451 } 1452} 1453