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\Gui\Song; 26 27use Ampache\Config\ConfigContainerInterface; 28use Ampache\Config\ConfigurationKeyEnum; 29use Ampache\Repository\Model\Catalog; 30use Ampache\Repository\Model\ModelFactoryInterface; 31use Ampache\Repository\Model\Rating; 32use Ampache\Repository\Model\Share; 33use Ampache\Repository\Model\Song; 34use Ampache\Repository\Model\Userflag; 35use Ampache\Module\Api\Ajax; 36use Ampache\Module\Application\Song\DeleteAction; 37use Ampache\Module\Authorization\AccessLevelEnum; 38use Ampache\Module\Authorization\GuiGatekeeperInterface; 39use Ampache\Module\Playback\Stream_Playlist; 40use Ampache\Module\System\Core; 41use Ampache\Module\Util\Ui; 42 43final class SongViewAdapter implements SongViewAdapterInterface 44{ 45 private ConfigContainerInterface $configContainer; 46 47 private ModelFactoryInterface $modelFactory; 48 49 private GuiGatekeeperInterface $gatekeeper; 50 51 private Song $song; 52 53 public function __construct( 54 ConfigContainerInterface $configContainer, 55 ModelFactoryInterface $modelFactory, 56 GuiGatekeeperInterface $gatekeeper, 57 Song $song 58 ) { 59 $this->configContainer = $configContainer; 60 $this->modelFactory = $modelFactory; 61 $this->gatekeeper = $gatekeeper; 62 $this->song = $song; 63 } 64 65 public function getId(): int 66 { 67 return $this->song->getId(); 68 } 69 70 public function getRating(): string 71 { 72 return Rating::show($this->song->getId(), 'song'); 73 } 74 75 public function getAverageRating(): string 76 { 77 $rating = $this->modelFactory->createRating( 78 $this->song->getId(), 79 'song' 80 ); 81 82 return (string) $rating->get_average_rating(); 83 } 84 85 public function canAutoplayNext(): bool 86 { 87 return Stream_Playlist::check_autoplay_next(); 88 } 89 90 public function canAppendNext(): bool 91 { 92 return Stream_Playlist::check_autoplay_append(); 93 } 94 95 public function getUserFlags(): string 96 { 97 return Userflag::show($this->song->getId(), 'song'); 98 } 99 100 public function getWaveformUrl(): string 101 { 102 return sprintf( 103 '%s/waveform.php?song_id=%d', 104 $this->configContainer->getWebPath(), 105 $this->song->getId() 106 ); 107 } 108 109 public function getDirectplayButton(): string 110 { 111 $songId = $this->song->getId(); 112 113 return Ajax::button( 114 '?page=stream&action=directplay&object_type=song&object_id=' . $songId, 115 'play', 116 T_('Play'), 117 'play_song_' . $songId 118 ); 119 } 120 121 public function getAutoplayNextButton(): string 122 { 123 $songId = $this->song->getId(); 124 125 return Ajax::button( 126 '?page=stream&action=directplay&object_type=song&object_id=' . $songId . '&playnext=true', 127 'play_next', 128 T_('Play next'), 129 'nextplay_song_' . $songId 130 ); 131 } 132 133 public function getAppendNextButton(): string 134 { 135 $songId = $this->song->getId(); 136 137 return Ajax::button( 138 '?page=stream&action=directplay&object_type=song&object_id=' . $songId . '&append=true', 139 'play_add', 140 T_('Play last'), 141 'addplay_song_' . $songId 142 ); 143 } 144 145 public function getCustomPlayActions(): string 146 { 147 $actions = Song::get_custom_play_actions(); 148 149 $buttons = ''; 150 foreach ($actions as $action) { 151 $buttons .= Ajax::button( 152 '?page=stream&action=directplay&object_type=song&object_id=' . $this->song->getId() . '&custom_play_action=' . $action['index'], 153 $action['icon'], 154 T_($action['title']), 155 $action['icon'] . '_song_' . $this->song->getId() 156 ); 157 } 158 159 return $buttons; 160 } 161 162 public function getTemporaryPlaylistButton(): string 163 { 164 $songId = $this->song->getId(); 165 166 return Ajax::button( 167 '?action=basket&type=song&id=' . $songId, 168 'add', 169 T_('Add to Temporary Playlist'), 170 'add_song_' . $songId 171 ); 172 } 173 174 public function canPostShout(): bool 175 { 176 return ( 177 $this->configContainer->isAuthenticationEnabled() === false || 178 $this->gatekeeper->mayAccess(AccessLevelEnum::TYPE_INTERFACE, AccessLevelEnum::LEVEL_USER) === true 179 ) && 180 $this->configContainer->isFeatureEnabled(ConfigurationKeyEnum::SOCIABLE); 181 } 182 183 public function getPostShoutUrl(): string 184 { 185 return sprintf( 186 '%s/shout.php?action=show_add_shout&type=song&id=%d', 187 $this->configContainer->getWebPath(), 188 $this->song->getId() 189 ); 190 } 191 192 public function getPostShoutIcon(): string 193 { 194 return Ui::get_icon('comment', T_('Post Shout')); 195 } 196 197 public function canShare(): bool 198 { 199 return $this->gatekeeper->mayAccess(AccessLevelEnum::TYPE_INTERFACE, AccessLevelEnum::LEVEL_USER) && 200 $this->configContainer->isFeatureEnabled(ConfigurationKeyEnum::SHARE); 201 } 202 203 public function getShareUi(): string 204 { 205 return Share::display_ui('song', $this->song->getId(), false); 206 } 207 208 public function canDownload(): bool 209 { 210 return $this->configContainer->isFeatureEnabled(ConfigurationKeyEnum::DOWNLOAD); 211 } 212 213 public function getExternalPlayUrl(): string 214 { 215 return $this->song->play_url( 216 '&action=download', 217 '', 218 false, 219 Core::get_global('user')->id 220 ); 221 } 222 223 public function getExternalPlayIcon(): string 224 { 225 return Ui::get_icon('link', T_('Link')); 226 } 227 228 public function getDownloadUrl(): string 229 { 230 return sprintf( 231 '%s/stream.php?action=download&song_id=%d', 232 $this->configContainer->getWebPath(), 233 $this->song->getId() 234 ); 235 } 236 237 public function getDownloadIcon(): string 238 { 239 return Ui::get_icon('download', T_('Download')); 240 } 241 242 public function canDisplayStats(): bool 243 { 244 $owner = $this->song->get_user_owner(); 245 246 return ( 247 ($owner !== null && $owner == $GLOBALS['user']->id) || 248 $this->gatekeeper->mayAccess(AccessLevelEnum::TYPE_INTERFACE, AccessLevelEnum::LEVEL_CONTENT_MANAGER) 249 ) && 250 $this->configContainer->isFeatureEnabled(ConfigurationKeyEnum::STATISTICAL_GRAPHS) && 251 is_dir(__DIR__ . '/../../../vendor/szymach/c-pchart/src/Chart/'); 252 } 253 254 public function getDisplayStatsUrl(): string 255 { 256 return sprintf( 257 '%s/stats.php?action=graph&object_type=song&object_id=%d', 258 $this->configContainer->getWebPath(), 259 $this->song->getId() 260 ); 261 } 262 263 public function getDisplayStatsIcon(): string 264 { 265 return Ui::get_icon('statistics', T_('Graphs')); 266 } 267 268 269 public function isEditable(): bool 270 { 271 return $this->gatekeeper->mayAccess(AccessLevelEnum::TYPE_INTERFACE, AccessLevelEnum::LEVEL_CONTENT_MANAGER) || ( 272 $this->song->get_user_owner() == Core::get_global('user')->id && 273 $this->configContainer->isFeatureEnabled(ConfigurationKeyEnum::UPLOAD_ALLOW_EDIT) === true 274 ); 275 } 276 277 public function getEditButtonTitle(): string 278 { 279 return T_('Song Edit'); 280 } 281 282 public function getEditIcon(): string 283 { 284 return Ui::get_icon('edit', T_('Edit')); 285 } 286 287 public function canToggleState(): bool 288 { 289 return $this->gatekeeper->mayAccess(AccessLevelEnum::TYPE_INTERFACE, AccessLevelEnum::LEVEL_MANAGER) || ( 290 $this->song->get_user_owner() == Core::get_global('user')->id && 291 $this->configContainer->isFeatureEnabled(ConfigurationKeyEnum::UPLOAD_ALLOW_EDIT) === true 292 ); 293 } 294 295 public function getToggleStateButton(): string 296 { 297 $songId = $this->song->getId(); 298 299 if ($this->song->enabled) { 300 $icon = 'disable'; 301 $icontext = T_('Disable'); 302 } else { 303 $icon = 'enable'; 304 $icontext = T_('Enable'); 305 } 306 307 return Ajax::button( 308 '?page=song&action=flip_state&song_id=' . $songId, 309 $icon, 310 $icontext, 311 'flip_song_' . $songId 312 ); 313 } 314 315 public function getDeletionUrl(): string 316 { 317 return sprintf( 318 '%s/song.php?action=%s&song_id=%d', 319 $this->configContainer->getWebPath(), 320 DeleteAction::REQUEST_KEY, 321 $this->song->getId() 322 ); 323 } 324 325 public function getDeletionIcon(): string 326 { 327 return Ui::get_icon('delete', T_('Delete')); 328 } 329 330 public function canBeDeleted(): bool 331 { 332 return Catalog::can_remove($this->song); 333 } 334 335 public function getProperties(): array 336 { 337 $songprops = []; 338 339 $songprops[T_('Title')] = scrub_out($this->song->title); 340 $songprops[T_('Song Artist')] = $this->song->f_artist_link; 341 if (!empty($this->song->f_albumartist_link)) { 342 $songprops[T_('Album Artist')] = $this->song->f_albumartist_link; 343 } 344 $songprops[T_('Album')] = $this->song->f_album_link . ($this->song->year ? " (" . scrub_out($this->song->year) . ")" : ""); 345 $songprops[T_('Composer')] = scrub_out($this->song->composer); 346 $songprops[T_('Genres')] = $this->song->f_tags; 347 $songprops[T_('Year')] = $this->song->year; 348 $songprops[T_('Original Year')] = scrub_out($this->song->get_album_original_year($this->song->album)); 349 $songprops[T_('Length')] = scrub_out($this->song->f_time); 350 $songprops[T_('Links')] = "<a href=\"http://www.google.com/search?q=%22" . rawurlencode($this->song->f_artist) . "%22+%22" . rawurlencode($this->song->f_title) . "%22\" target=\"_blank\">" . UI::get_icon('google', T_('Search on Google ...')) . "</a>"; 351 $songprops[T_('Links')] .= " <a href=\"https://www.duckduckgo.com/?q=%22" . rawurlencode($this->song->f_artist) . "%22+%22" . rawurlencode($this->song->f_title) . "%22\" target=\"_blank\">" . UI::get_icon('duckduckgo', T_('Search on DuckDuckGo ...')) . "</a>"; 352 $songprops[T_('Links')] .= " <a href=\"http://www.last.fm/search?q=%22" . rawurlencode($this->song->f_artist) . "%22+%22" . rawurlencode($this->song->f_title) . "%22&type=track\" target=\"_blank\">" . UI::get_icon('lastfm', T_('Search on Last.fm ...')) . "</a>"; 353 if ($this->song->mbid) { 354 $songprops[T_('Links')] .= " <a href=\"https://musicbrainz.org/recording/" . $this->song->mbid . "\" target=\"_blank\">" . UI::get_icon('musicbrainz', T_('Search on Musicbrainz ...')) . "</a>"; 355 } else { 356 $songprops[T_('Links')] .= " <a href=\"https://musicbrainz.org/taglookup?tag-lookup.artist=%22" . rawurlencode($this->song->f_artist) . "%22&tag-lookup.track=%22" . rawurlencode($this->song->f_title) . "%22\" target=\"_blank\">" . UI::get_icon('musicbrainz', T_('Search on Musicbrainz ...')) . "</a>"; 357 } 358 $songprops[T_('Comment')] = scrub_out($this->song->comment); 359 $label_string = ''; 360 foreach (array_map('trim', explode(';', $this->song->label)) as $label_name) { 361 $label_string .= "<a href=\"" . $this->configContainer->getWebPath() . "/labels.php?action=show&name=" . scrub_out($label_name) . "\">" . scrub_out($label_name) . "</a> "; 362 } 363 $songprops[T_('Label')] = $this->configContainer->isFeatureEnabled(ConfigurationKeyEnum::LABEL) ? $label_string : scrub_out($this->song->label); 364 $songprops[T_('Song Language')] = scrub_out($this->song->language); 365 $songprops[T_('Catalog Number')] = scrub_out($this->song->get_album_catalog_number($this->song->album)); 366 $songprops[T_('Barcode')] = scrub_out($this->song->get_album_barcode($this->song->album)); 367 $songprops[T_('Bitrate')] = scrub_out($this->song->f_bitrate); 368 $songprops[T_('Channels')] = scrub_out($this->song->channels); 369 $songprops[T_('Song MBID')] = scrub_out($this->song->mbid); 370 $songprops[T_('Album MBID')] = scrub_out($this->song->album_mbid); 371 $songprops[T_('Artist MBID')] = scrub_out($this->song->artist_mbid); 372 if ($this->song->replaygain_track_gain !== null) { 373 $songprops[T_('ReplayGain Track Gain')] = scrub_out($this->song->replaygain_track_gain); 374 } 375 if ($this->song->replaygain_album_gain !== null) { 376 $songprops[T_('ReplayGain Album Gain')] = scrub_out($this->song->replaygain_album_gain); 377 } 378 if ($this->gatekeeper->mayAccess(AccessLevelEnum::TYPE_INTERFACE, AccessLevelEnum::LEVEL_MANAGER)) { 379 $songprops[T_('Filename')] = scrub_out($this->song->file) . " " . $this->song->f_size; 380 } 381 if ($this->song->update_time) { 382 $songprops[T_('Last Updated')] = get_datetime((int) $this->song->update_time); 383 } 384 $songprops[T_('Added')] = get_datetime((int) $this->song->addition_time); 385 if ($this->configContainer->isFeatureEnabled(ConfigurationKeyEnum::SHOW_PLAYED_TIMES)) { 386 $songprops[T_('# Played')] = scrub_out($this->song->object_cnt); 387 } 388 if ($this->configContainer->isFeatureEnabled(ConfigurationKeyEnum::SHOW_SKIPPED_TIMES)) { 389 $songprops[T_('# Skipped')] = scrub_out($this->song->skip_cnt); 390 } 391 392 if ($this->configContainer->isFeatureEnabled(ConfigurationKeyEnum::SHOW_LYRICS)) { 393 $songprops[T_('Lyrics')] = $this->song->f_lyrics; 394 } 395 396 if ($this->configContainer->isFeatureEnabled(ConfigurationKeyEnum::LICENSING) && $this->song->license) { 397 $songprops[T_('Licensing')] = $this->song->f_license; 398 } 399 400 $owner_id = $this->song->get_user_owner(); 401 if ($this->configContainer->isFeatureEnabled(ConfigurationKeyEnum::SOCIABLE) && $owner_id > 0) { 402 $owner = $this->modelFactory->createUser($owner_id); 403 $owner->format(); 404 $songprops[T_('Uploaded by')] = $owner->f_link; 405 } 406 407 return $songprops; 408 } 409 410 public function canEditPlaylist(): bool 411 { 412 return $this->gatekeeper->mayAccess(AccessLevelEnum::TYPE_INTERFACE, AccessLevelEnum::LEVEL_USER); 413 } 414 415 public function getAddToPlaylistIcon(): string 416 { 417 return Ui::get_icon('playlist_add', T_('Add to playlist')); 418 } 419 420 public function canBeReordered(): bool 421 { 422 return $this->gatekeeper->mayAccess(AccessLevelEnum::TYPE_INTERFACE, AccessLevelEnum::LEVEL_CONTENT_MANAGER); 423 } 424 425 public function getReorderIcon(): string 426 { 427 return Ui::get_icon('drag', T_('Reorder')); 428 } 429 430 public function getPreferencesIcon(): string 431 { 432 return Ui::get_icon('preferences', T_('Song Information')); 433 } 434 435 public function getTrackNumber(): string 436 { 437 return $this->song->f_track; 438 } 439 440 public function getSongUrl(): string 441 { 442 return $this->song->link; 443 } 444 445 public function getSongLink(): string 446 { 447 return $this->song->f_link; 448 } 449 450 public function getArtistLink(): string 451 { 452 return $this->song->f_artist_link; 453 } 454 455 public function getAlbumLink(): string 456 { 457 return $this->song->f_album_link; 458 } 459 460 public function getYear(): int 461 { 462 return $this->song->year; 463 } 464 465 public function getGenre(): string 466 { 467 return $this->song->f_tags; 468 } 469 470 public function getPlayDuration(): string 471 { 472 return $this->song->f_time; 473 } 474 475 public function getLicenseLink(): string 476 { 477 return (string) $this->song->f_license; 478 } 479 480 public function getNumberPlayed(): int 481 { 482 return $this->song->object_cnt; 483 } 484 485 public function getNumberSkipped(): int 486 { 487 return $this->song->skip_cnt; 488 } 489} 490