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\Api\Ajax; 28use Ampache\Config\AmpConfig; 29use Ampache\Module\Util\AjaxUriRetrieverInterface; 30use Ampache\Module\Util\ObjectTypeToClassNameMapper; 31use Ampache\Module\Util\Ui; 32 33/** 34 * Browse Class 35 * 36 * This handles all of the sql/filtering 37 * on the data before it's thrown out to the templates 38 * it also handles pulling back the object_ids and then 39 * calling the correct template for the object we are displaying 40 * 41 */ 42class Browse extends Query 43{ 44 /** 45 * @var boolean $show_header 46 */ 47 public $show_header; 48 49 /** 50 * @var integer $duration 51 */ 52 public $duration; 53 54 /** 55 * Constructor. 56 * 57 * @param integer|null $browse_id 58 * @param boolean $cached 59 */ 60 public function __construct($browse_id = null, $cached = true) 61 { 62 parent::__construct($browse_id, $cached); 63 64 if (!$browse_id) { 65 $this->set_use_pages(true); 66 $this->set_use_alpha(false); 67 $this->set_grid_view(true); 68 } 69 $this->show_header = true; 70 } 71 72 public function getId(): int 73 { 74 return (int) $this->id; 75 } 76 77 /** 78 * set_simple_browse 79 * This sets the current browse object to a 'simple' browse method 80 * which means use the base query provided and expand from there 81 * 82 * @param boolean $value 83 */ 84 public function set_simple_browse($value) 85 { 86 $this->set_is_simple($value); 87 } // set_simple_browse 88 89 /** 90 * add_supplemental_object 91 * Legacy function, need to find a better way to do that 92 * 93 * @param string $class 94 * @param integer $uid 95 * @return boolean 96 */ 97 public function add_supplemental_object($class, $uid) 98 { 99 $_SESSION['browse']['supplemental'][$this->id][$class] = (int)($uid); 100 101 return true; 102 } // add_supplemental_object 103 104 /** 105 * get_supplemental_objects 106 * This returns an array of 'class', 'id' for additional objects that 107 * need to be created before we start this whole browsing thing. 108 * 109 * @return array 110 */ 111 public function get_supplemental_objects() 112 { 113 $objects = isset($_SESSION['browse']['supplemental'][$this->id]) ? $_SESSION['browse']['supplemental'][$this->id] : ''; 114 115 if (!is_array($objects)) { 116 $objects = array(); 117 } 118 119 return $objects; 120 } // get_supplemental_objects 121 122 /** 123 * update_browse_from_session 124 * Restore the previous start index from something saved into the current session. 125 */ 126 public function update_browse_from_session() 127 { 128 if ($this->is_simple() && $this->get_start() == 0) { 129 $name = 'browse_current_' . $this->get_type(); 130 if (isset($_SESSION[$name]) && isset($_SESSION[$name]['start']) && $_SESSION[$name]['start'] > 0) { 131 // Checking if value is suitable 132 $start = $_SESSION[$name]['start']; 133 if ($this->get_offset() > 0) { 134 $set_page = floor($start / $this->get_offset()); 135 if ($this->get_total() > $this->get_offset()) { 136 $total_pages = ceil($this->get_total() / $this->get_offset()); 137 } else { 138 $total_pages = 0; 139 } 140 141 if ($set_page >= 0 && $set_page <= $total_pages) { 142 $this->set_start($start); 143 } 144 } 145 } 146 } 147 } 148 149 /** 150 * show_objects 151 * This takes an array of objects 152 * and requires the correct template based on the 153 * type that we are currently browsing 154 * 155 * @param array $object_ids 156 * @param boolean|array|string $argument 157 */ 158 public function show_objects($object_ids = array(), $argument = false) 159 { 160 if ($this->is_simple() || !is_array($object_ids) || empty($object_ids)) { 161 $object_ids = $this->get_saved(); 162 } else { 163 $this->save_objects($object_ids); 164 } 165 166 // Limit is based on the user's preferences if this is not a 167 // simple browse because we've got too much here 168 if ($this->get_start() >= 0 && (count($object_ids) > $this->get_start()) && !$this->is_simple()) { 169 $object_ids = array_slice($object_ids, $this->get_start(), $this->get_offset(), true); 170 } else { 171 if (!count($object_ids)) { 172 $this->set_total(0); 173 } 174 } 175 176 // Load any additional object we need for this 177 $extra_objects = $this->get_supplemental_objects(); 178 $browse = $this; 179 180 foreach ($extra_objects as $type => $id) { 181 $class_name = ObjectTypeToClassNameMapper::map($type); 182 ${$type} = new $class_name($id); 183 } 184 185 $match = ''; 186 // Format any matches we have so we can show them to the masses 187 if ($filter_value = $this->get_filter('alpha_match')) { 188 $match = ' (' . (string)$filter_value . ')'; 189 } elseif ($filter_value = $this->get_filter('starts_with')) { 190 $match = ' (' . (string)$filter_value . ')'; 191 /*} elseif ($filter_value = $this->get_filter('regex_match')) { 192 $match = ' (' . (string) $filter_value . ')'; 193 } elseif ($filter_value = $this->get_filter('regex_not_match')) { 194 $match = ' (' . (string) $filter_value . ')';*/ 195 } elseif ($filter_value = $this->get_filter('catalog')) { 196 // Get the catalog title 197 $catalog = Catalog::create_from_id((int)((string)$filter_value)); 198 $match = ' (' . $catalog->name . ')'; 199 } 200 201 $type = $this->get_type(); 202 203 // Update the session value only if it's allowed on the current browser 204 if ($this->is_update_session()) { 205 $_SESSION['browse_current_' . $type]['start'] = $browse->get_start(); 206 } 207 208 // Set the correct classes based on type 209 $class = "box browse_" . $type; 210 debug_event(self::class, 'Show objects called for type {' . $type . '}', 5); 211 212 // hide some of the useless columns in a browse 213 $hide_columns = array(); 214 $argument_param = ''; 215 if (is_array($argument)) { 216 if (is_array($argument['hide'])) { 217 $hide_columns = $argument['hide']; 218 } 219 if (!empty($hide_columns)) { 220 $argument_param = '&hide='; 221 foreach ($hide_columns as $column) { 222 $argument_param .= scrub_in((string)$column) . ','; 223 } 224 $argument_param = rtrim($argument_param, ','); 225 } 226 } else { 227 $argument_param = ($argument) 228 ? '&argument=' . scrub_in((string)$argument) 229 : ''; 230 } 231 232 $limit_threshold = $this->get_threshold(); 233 // Switch on the type of browsing we're doing 234 switch ($type) { 235 case 'song': 236 $box_title = T_('Songs') . $match; 237 Song::build_cache($object_ids, $limit_threshold); 238 $box_req = Ui::find_template('show_songs.inc.php'); 239 break; 240 case 'album': 241 Album::build_cache($object_ids); 242 $box_title = T_('Albums') . $match; 243 $allow_group_disks = false; 244 if (is_array($argument)) { 245 $allow_group_disks = $argument['group_disks']; 246 if ($argument['title']) { 247 $box_title = $argument['title']; 248 } 249 } 250 if (AmpConfig::get('album_group')) { 251 $allow_group_disks = true; 252 } 253 $box_req = Ui::find_template('show_albums.inc.php'); 254 break; 255 case 'user': 256 $box_title = T_('Browse Users') . $match; 257 $box_req = Ui::find_template('show_users.inc.php'); 258 break; 259 case 'artist': 260 $box_title = ($this->is_album_artist()) 261 ? T_('Album Artists') . $match 262 : T_('Artists') . $match; 263 Artist::build_cache($object_ids, true, $limit_threshold); 264 $box_req = Ui::find_template('show_artists.inc.php'); 265 break; 266 case 'live_stream': 267 $box_title = T_('Radio Stations') . $match; 268 $box_req = Ui::find_template('show_live_streams.inc.php'); 269 break; 270 case 'playlist': 271 Playlist::build_cache($object_ids); 272 $box_title = T_('Playlists') . $match; 273 $box_req = Ui::find_template('show_playlists.inc.php'); 274 break; 275 case 'playlist_media': 276 $box_title = T_('Playlist Items') . $match; 277 $box_req = Ui::find_template('show_playlist_medias.inc.php'); 278 break; 279 case 'playlist_localplay': 280 $box_title = T_('Current Playlist'); 281 $box_req = Ui::find_template('show_localplay_playlist.inc.php'); 282 Ui::show_box_bottom(); 283 break; 284 case 'smartplaylist': 285 $box_title = T_('Smart Playlists') . $match; 286 $box_req = Ui::find_template('show_searches.inc.php'); 287 break; 288 case 'catalog': 289 $box_title = T_('Catalogs'); 290 $box_req = Ui::find_template('show_catalogs.inc.php'); 291 break; 292 case 'shoutbox': 293 $box_title = T_('Shoutbox Records'); 294 $box_req = Ui::find_template('show_manage_shoutbox.inc.php'); 295 break; 296 case 'tag': 297 Tag::build_cache($object_ids); 298 $box_title = T_('Genres'); 299 $box_req = Ui::find_template('show_tagcloud.inc.php'); 300 break; 301 case 'video': 302 Video::build_cache($object_ids); 303 $video_type = 'video'; 304 $box_title = T_('Videos'); 305 $box_req = Ui::find_template('show_videos.inc.php'); 306 break; 307 case 'democratic': 308 $box_title = T_('Democratic Playlist'); 309 $box_req = Ui::find_template('show_democratic_playlist.inc.php'); 310 break; 311 case 'wanted': 312 $box_title = T_('Wanted Albums'); 313 $box_req = Ui::find_template('show_wanted_albums.inc.php'); 314 break; 315 case 'share': 316 $box_title = T_('Shares'); 317 $box_req = Ui::find_template('show_shared_objects.inc.php'); 318 break; 319 case 'song_preview': 320 $box_title = T_('Songs'); 321 $box_req = Ui::find_template('show_song_previews.inc.php'); 322 break; 323 case 'channel': 324 $box_title = T_('Channels'); 325 $box_req = Ui::find_template('show_channels.inc.php'); 326 break; 327 case 'broadcast': 328 $box_title = T_('Broadcasts'); 329 $box_req = Ui::find_template('show_broadcasts.inc.php'); 330 break; 331 case 'license': 332 $box_title = T_('Media Licenses'); 333 $box_req = Ui::find_template('show_manage_license.inc.php'); 334 break; 335 case 'tvshow': 336 $box_title = T_('TV Shows'); 337 $box_req = Ui::find_template('show_tvshows.inc.php'); 338 break; 339 case 'tvshow_season': 340 $box_title = T_('Seasons'); 341 $box_req = Ui::find_template('show_tvshow_seasons.inc.php'); 342 break; 343 case 'tvshow_episode': 344 $box_title = T_('Episodes'); 345 $video_type = $type; 346 $box_req = Ui::find_template('show_videos.inc.php'); 347 break; 348 case 'movie': 349 $box_title = T_('Movies'); 350 $video_type = $type; 351 $box_req = Ui::find_template('show_videos.inc.php'); 352 break; 353 case 'clip': 354 $box_title = T_('Clips'); 355 $video_type = $type; 356 $box_req = Ui::find_template('show_videos.inc.php'); 357 break; 358 case 'personal_video': 359 $box_title = T_('Personal Videos'); 360 $video_type = $type; 361 $box_req = Ui::find_template('show_videos.inc.php'); 362 break; 363 case 'label': 364 $box_title = T_('Labels'); 365 $box_req = Ui::find_template('show_labels.inc.php'); 366 break; 367 case 'pvmsg': 368 $box_title = T_('Private Messages'); 369 $box_req = Ui::find_template('show_pvmsgs.inc.php'); 370 break; 371 case 'podcast': 372 $box_title = T_('Podcasts'); 373 $box_req = Ui::find_template('show_podcasts.inc.php'); 374 break; 375 case 'podcast_episode': 376 $box_title = T_('Podcast Episodes'); 377 $box_req = Ui::find_template('show_podcast_episodes.inc.php'); 378 break; 379 default: 380 break; 381 } // end switch on type 382 383 Ajax::start_container($this->get_content_div(), 'browse_content'); 384 if ($this->is_show_header()) { 385 if (isset($box_req) && isset($box_title)) { 386 Ui::show_box_top($box_title, $class); 387 } 388 } 389 390 if (isset($box_req)) { 391 require $box_req; 392 } 393 394 if ($this->is_show_header()) { 395 if (isset($box_req)) { 396 Ui::show_box_bottom(); 397 } 398 echo '<script>'; 399 echo Ajax::action('?page=browse&action=get_filters&browse_id=' . $this->id . $argument_param, ''); 400 echo ';</script>'; 401 } else { 402 if (!$this->is_use_pages()) { 403 $this->show_next_link($argument); 404 } 405 } 406 Ajax::end_container(); 407 } // show_object 408 409 /** 410 * @param $argument 411 */ 412 public function show_next_link($argument = null) 413 { 414 // FIXME Can be removed if Browse gets instantiated by the factory 415 global $dic; 416 417 $limit = $this->get_offset(); 418 $start = $this->get_start(); 419 $total = $this->get_total(); 420 $next_offset = $start + $limit; 421 if ($next_offset <= $total) { 422 echo '<a class="jscroll-next" href="' . $dic->get(AjaxUriRetrieverInterface::class)->getAjaxUri() . '?page=browse&action=page&browse_id=' . $this->id . '&start=' . $next_offset . '&xoutput=raw&xoutputnode=' . $this->get_content_div() . '&show_header=false' . $argument . '">' . T_('More') . '</a>'; 423 } 424 } 425 426 /** 427 * 428 * @param string $type 429 * @param string $custom_base 430 */ 431 public function set_type($type, $custom_base = '') 432 { 433 $name = 'browse_' . $type . '_pages'; 434 if ((filter_has_var(INPUT_COOKIE, $name))) { 435 $this->set_use_pages(filter_input(INPUT_COOKIE, $name, FILTER_SANITIZE_STRING, 436 FILTER_FLAG_NO_ENCODE_QUOTES) == 'true'); 437 } 438 $name = 'browse_' . $type . '_alpha'; 439 if ((filter_has_var(INPUT_COOKIE, $name))) { 440 $this->set_use_alpha(filter_input(INPUT_COOKIE, $name, FILTER_SANITIZE_STRING, 441 FILTER_FLAG_NO_ENCODE_QUOTES) == 'true'); 442 } else { 443 $default_alpha = (!AmpConfig::get('libitem_browse_alpha')) ? array() : explode(",", 444 AmpConfig::get('libitem_browse_alpha')); 445 if (in_array($type, $default_alpha)) { 446 $this->set_use_alpha(true, false); 447 } 448 } 449 $name = 'browse_' . $type . '_grid_view'; 450 if ((filter_has_var(INPUT_COOKIE, $name))) { 451 $this->set_grid_view(filter_input(INPUT_COOKIE, $name, FILTER_SANITIZE_STRING, 452 FILTER_FLAG_NO_ENCODE_QUOTES) == 'true'); 453 } 454 455 parent::set_type($type, $custom_base); 456 } 457 458 /** 459 * 460 * @param string $option 461 * @param string $value 462 */ 463 public function save_cookie_params($option, $value) 464 { 465 if ($this->get_type()) { 466 $remember_length = time() + 31536000; 467 $cookie_options = [ 468 'expires' => $remember_length, 469 'path' => AmpConfig::get('cookie_path'), 470 'domain' => AmpConfig::get('cookie_domain'), 471 'secure' => make_bool(AmpConfig::get('cookie_secure')), 472 'samesite' => 'Strict' 473 ]; 474 setcookie('browse_' . $this->get_type() . '_' . $option, $value, $cookie_options); 475 } 476 } 477 478 /** 479 * 480 * @param boolean $use_pages 481 * @param boolean $savecookie 482 */ 483 public function set_use_pages($use_pages, $savecookie = true) 484 { 485 if ($savecookie) { 486 $this->save_cookie_params('pages', $use_pages ? 'true' : 'false'); 487 } 488 $this->_state['use_pages'] = $use_pages; 489 } 490 491 /** 492 * 493 * @return boolean 494 */ 495 public function is_use_pages() 496 { 497 return make_bool($this->_state['use_pages']); 498 } 499 500 /** 501 * 502 * @param boolean $mashup 503 */ 504 public function set_mashup($mashup) 505 { 506 $this->_state['mashup'] = $mashup; 507 } 508 509 /** 510 * 511 * @return boolean 512 */ 513 public function is_mashup() 514 { 515 return make_bool($this->_state['mashup']); 516 } 517 518 /** 519 * 520 * @param boolean $album_artist 521 */ 522 public function set_album_artist($album_artist) 523 { 524 $this->_state['album_artist'] = $album_artist; 525 } 526 527 /** 528 * 529 * @return boolean 530 */ 531 public function is_album_artist() 532 { 533 return make_bool($this->_state['album_artist']); 534 } 535 536 /** 537 * 538 * @param boolean $grid_view 539 * @param boolean $savecookie 540 */ 541 public function set_grid_view($grid_view, $savecookie = true) 542 { 543 if ($savecookie) { 544 $this->save_cookie_params('grid_view', $grid_view ? 'true' : 'false'); 545 } 546 $this->_state['grid_view'] = $grid_view; 547 } 548 549 /** 550 * 551 * @return boolean 552 */ 553 public function is_grid_view() 554 { 555 return make_bool($this->_state['grid_view']); 556 } 557 558 /** 559 * 560 * @param boolean $use_alpha 561 * @param boolean $savecookie 562 */ 563 public function set_use_alpha($use_alpha, $savecookie = true) 564 { 565 if ($savecookie) { 566 $this->save_cookie_params('alpha', $use_alpha ? 'true' : 'false'); 567 } 568 $this->_state['use_alpha'] = $use_alpha; 569 570 if ($use_alpha) { 571 if (count($this->_state['filter']) == 0) { 572 $this->set_filter('regex_match', '^A'); 573 } 574 } else { 575 $this->set_filter('regex_not_match', ''); 576 } 577 } 578 579 /** 580 * 581 * @return boolean 582 */ 583 public function is_use_alpha() 584 { 585 return make_bool($this->_state['use_alpha']); 586 } 587 588 /** 589 * 590 * @param boolean $show_header 591 */ 592 public function set_show_header($show_header) 593 { 594 $this->show_header = $show_header; 595 } 596 597 /** 598 * Allow the current page to be save into the current session 599 * @param boolean $update_session 600 */ 601 public function set_update_session($update_session) 602 { 603 $this->_state['update_session'] = $update_session; 604 } 605 606 /** 607 * 608 * @return boolean 609 */ 610 public function is_show_header() 611 { 612 return $this->show_header; 613 } 614 615 /** 616 * 617 * @return boolean 618 */ 619 public function is_update_session() 620 { 621 return make_bool($this->_state['update_session']); 622 } 623 624 /** 625 * 626 * @param string $threshold 627 */ 628 public function set_threshold($threshold) 629 { 630 $this->_state['threshold'] = $threshold; 631 } 632 633 /** 634 * 635 * @return string 636 */ 637 public function get_threshold() 638 { 639 return (string)$this->_state['threshold']; 640 } 641 642 /** 643 * 644 * @return string 645 */ 646 public function get_css_class() 647 { 648 $css = ''; 649 if (!$this->_state['grid_view']) { 650 $css = 'disablegv'; 651 } 652 653 return $css; 654 } 655} 656