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')] .= "&nbsp;<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')] .= "&nbsp;<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')] .= "&nbsp;<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')] .= "&nbsp;<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