Passed
Push — master ( a4260e...ab8f86 )
by Pauli
13:01
created

TrackMapper::findByFileIds()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 2
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
/**
4
 * ownCloud - Music app
5
 *
6
 * This file is licensed under the Affero General Public License version 3 or
7
 * later. See the COPYING file.
8
 *
9
 * @author Morris Jobke <[email protected]>
10
 * @author Pauli Järvinen <[email protected]>
11
 * @copyright Morris Jobke 2013, 2014
12
 * @copyright Pauli Järvinen 2016 - 2024
13
 */
14
15
namespace OCA\Music\Db;
16
17
use OCP\IConfig;
18
use OCP\IDBConnection;
19
20
/**
21
 * @phpstan-extends BaseMapper<Track>
22
 */
23
class TrackMapper extends BaseMapper {
24
	public function __construct(IDBConnection $db, IConfig $config) {
25
		parent::__construct($db, $config, 'music_tracks', Track::class, 'title', 'album_id');
26
	}
27
28
	/**
29
	 * Override the base implementation to include data from multiple tables
30
	 *
31
	 * {@inheritdoc}
32
	 * @see BaseMapper::selectEntities()
33
	 */
34
	protected function selectEntities(string $condition, string $extension=null) : string {
35
		return "SELECT `*PREFIX*music_tracks`.*, `file`.`name` AS `filename`, `file`.`size`, `file`.`mtime` AS `file_mod_time`,
36
						`album`.`name` AS `album_name`, `artist`.`name` AS `artist_name`, `genre`.`name` AS `genre_name`
37
				FROM `*PREFIX*music_tracks`
38
				INNER JOIN `*PREFIX*filecache` `file`
39
				ON `*PREFIX*music_tracks`.`file_id` = `file`.`fileid`
40
				INNER JOIN `*PREFIX*music_albums` `album`
41
				ON `*PREFIX*music_tracks`.`album_id` = `album`.`id`
42
				INNER JOIN `*PREFIX*music_artists` `artist`
43
				ON `*PREFIX*music_tracks`.`artist_id` = `artist`.`id`
44
				LEFT JOIN `*PREFIX*music_genres` `genre`
45
				ON `*PREFIX*music_tracks`.`genre_id` = `genre`.`id`
46
				WHERE $condition $extension";
47
	}
48
49
	/**
50
	 * Overridden from the base implementation to add support for sorting by artist, play_count, and last_played.
51
	 *
52
	 * {@inheritdoc}
53
	 * @see BaseMapper::formatSortingClause()
54
	 */
55
	protected function formatSortingClause(int $sortBy, bool $invertSort = false) : ?string {
56
		switch ($sortBy) {
57
			case SortBy::Parent:
58
				$dir = $invertSort ? 'DESC' : 'ASC';
59
				// Note: the alternative form "LOWER(`artist_name`) wouldn't work on PostgreSQL, see https://github.com/owncloud/music/issues/1046 for a similar case
60
				return "ORDER BY LOWER(`artist`.`name`) $dir, LOWER(`title`) $dir";
61
			case SortBy::PlayCount:
62
				$dir = $invertSort ? 'ASC' : 'DESC';
63
				return "ORDER BY `play_count` $dir";
64
			case SortBy::LastPlayed:
65
				$dir = $invertSort ? 'ASC' : 'DESC';
66
				return "ORDER BY `last_played` $dir";
67
			default:
68
				return parent::formatSortingClause($sortBy, $invertSort);
69
		}
70
	}
71
72
	/**
73
	 * Returns all tracks of the given artist (both album and track artists are considered)
74
	 * @param int[] $artistIds
75
	 * @return Track[]
76
	 */
77
	public function findAllByArtist(array $artistIds, string $userId, ?int $limit=null, ?int $offset=null) : array {
78
		$questionMarks = $this->questionMarks(\count($artistIds));
79
		$sql = $this->selectUserEntities(
80
				"`artist_id` IN $questionMarks OR `album_id` IN (SELECT `id` from `*PREFIX*music_albums` WHERE `album_artist_id` IN $questionMarks)",
81
				'ORDER BY LOWER(`title`)');
82
		$params = \array_merge([$userId], $artistIds, $artistIds);
83
		return $this->findEntities($sql, $params, $limit, $offset);
84
	}
85
86
	/**
87
	 * @param int[] $albumIds
88
	 * @return Track[]
89
	 */
90
	public function findAllByAlbum(array $albumIds, string $userId, ?int $artistId=null, ?int $limit=null, ?int $offset=null) : array {
91
		$condition = '`album_id` IN ' . $this->questionMarks(\count($albumIds));
92
		$params = \array_merge([$userId], $albumIds);
93
94
		if ($artistId !== null) {
95
			$condition .= ' AND `artist_id` = ? ';
96
			$params[] = $artistId;
97
		}
98
99
		$sql = $this->selectUserEntities($condition,
100
				'ORDER BY `*PREFIX*music_tracks`.`disk`, `number`, LOWER(`title`)');
101
		return $this->findEntities($sql, $params, $limit, $offset);
102
	}
103
104
	/**
105
	 * @return Track[]
106
	 */
107
	public function findAllByFolder(int $folderId, string $userId, ?int $limit=null, ?int $offset=null) : array {
108
		$sql = $this->selectUserEntities('`file`.`parent` = ?', 'ORDER BY LOWER(`title`)');
109
		$params = [$userId, $folderId];
110
		return $this->findEntities($sql, $params, $limit, $offset);
111
	}
112
113
	/**
114
	 * @return Track[]
115
	 */
116
	public function findAllByGenre(int $genreId, string $userId, ?int $limit=null, ?int $offset=null) : array {
117
		$sql = $this->selectUserEntities('`genre_id` = ?', 'ORDER BY LOWER(`title`)');
118
		$params = [$userId, $genreId];
119
		return $this->findEntities($sql, $params, $limit, $offset);
120
	}
121
122
	/**
123
	 * @param string $userId
124
	 * @return int[]
125
	 */
126
	public function findAllFileIds(string $userId) : array {
127
		$sql = 'SELECT `file_id` FROM `*PREFIX*music_tracks` WHERE `user_id` = ?';
128
		$result = $this->execute($sql, [$userId]);
129
130
		return \array_map(function ($i) {
131
			return (int)$i['file_id'];
132
		}, $result->fetchAll());
133
	}
134
135
	/**
136
	 * Find a track of user matching a file ID
137
	 * @throws \OCP\AppFramework\Db\DoesNotExistException if not found
138
	 */
139
	public function findByFileId(int $fileId, string $userId) : Track {
140
		$sql = $this->selectUserEntities('`file_id` = ?');
141
		$params = [$userId, $fileId];
142
		return $this->findEntity($sql, $params);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->findEntity($sql, $params) returns the type OCP\AppFramework\Db\Entity which includes types incompatible with the type-hinted return OCA\Music\Db\Track.
Loading history...
143
	}
144
145
	/**
146
	 * Find tracks of user with multiple file IDs
147
	 * @param integer[] $fileIds
148
	 * @param string[] $userIds
149
	 * @return Track[]
150
	 */
151
	public function findByFileIds(array $fileIds, array $userIds) : array {
152
		$sql = $this->selectEntities(
153
				'`*PREFIX*music_tracks`.`user_id` IN ' . $this->questionMarks(\count($userIds)) .
154
				' AND `file_id` IN '. $this->questionMarks(\count($fileIds)));
155
		$params = \array_merge($userIds, $fileIds);
156
		return $this->findEntities($sql, $params);
157
	}
158
159
	/**
160
	 * Finds tracks of all users matching one or multiple file IDs
161
	 * @param integer[] $fileIds
162
	 * @return Track[]
163
	 */
164
	public function findAllByFileIds(array $fileIds) : array {
165
		$sql = $this->selectEntities('`file_id` IN '.
166
				$this->questionMarks(\count($fileIds)));
167
		return $this->findEntities($sql, $fileIds);
168
	}
169
170
	public function countByArtist(int $artistId) : int {
171
		$sql = 'SELECT COUNT(*) AS `count` FROM `*PREFIX*music_tracks` WHERE `artist_id` = ?';
172
		$result = $this->execute($sql, [$artistId]);
173
		$row = $result->fetch();
174
		return (int)$row['count'];
175
	}
176
177
	public function countByAlbum(int $albumId) : int {
178
		$sql = 'SELECT COUNT(*) AS `count` FROM `*PREFIX*music_tracks` WHERE `album_id` = ?';
179
		$result = $this->execute($sql, [$albumId]);
180
		$row = $result->fetch();
181
		return (int)$row['count'];
182
	}
183
184
	/**
185
	 * @return integer Duration in seconds
186
	 */
187
	public function totalDurationOfAlbum(int $albumId) : int {
188
		$sql = 'SELECT SUM(`length`) AS `duration` FROM `*PREFIX*music_tracks` WHERE `album_id` = ?';
189
		$result = $this->execute($sql, [$albumId]);
190
		$row = $result->fetch();
191
		return (int)$row['duration'];
192
	}
193
194
	/**
195
	 * @return integer Duration in seconds
196
	 */
197
	public function totalDurationByArtist(int $artistId) : int {
198
		$sql = 'SELECT SUM(`length`) AS `duration` FROM `*PREFIX*music_tracks` WHERE `artist_id` = ?';
199
		$result = $this->execute($sql, [$artistId]);
200
		$row = $result->fetch();
201
		return (int)$row['duration'];
202
	}
203
204
	/**
205
	 * Get durations of the given tracks.
206
	 * @param integer[] $trackIds
207
	 * @return array {int => int} where keys are track IDs and values are corresponding durations
208
	 */
209
	public function getDurations(array $trackIds) : array {
210
		$result = [];
211
212
		if (!empty($trackIds)) {
213
			$sql = 'SELECT `id`, `length` FROM `*PREFIX*music_tracks` WHERE `id` IN ' .
214
						$this->questionMarks(\count($trackIds));
215
			$rows = $this->execute($sql, $trackIds)->fetchAll();
216
			foreach ($rows as $row) {
217
				$result[$row['id']] = (int)$row['length'];
218
			}
219
		}
220
		return $result;
221
	}
222
223
	/**
224
	 * @return Track[]
225
	 */
226
	public function findAllByNameRecursive(string $name, string $userId, ?int $limit=null, ?int $offset=null) {
227
		$condition = '( LOWER(`artist`.`name`) LIKE LOWER(?) OR
228
						LOWER(`album`.`name`) LIKE LOWER(?) OR
229
						LOWER(`title`) LIKE LOWER(?) )';
230
		$sql = $this->selectUserEntities($condition, 'ORDER BY LOWER(`title`)');
231
		$name = BaseMapper::prepareSubstringSearchPattern($name);
232
		$params = [$userId, $name, $name, $name];
233
		return $this->findEntities($sql, $params, $limit, $offset);
234
	}
235
236
	/**
237
	 * Returns all tracks specified by name, artist name, and/or album name
238
	 * @param string|null $name the name of the track
239
	 * @param string|null $artistName the name of the artist
240
	 * @param string $userId the name of the user
241
	 * @return Track[] Tracks matching the criteria
242
	 */
243
	public function findAllByNameArtistOrAlbum(?string $name, ?string $artistName, ?string $albumName, string $userId) : array {
244
		$sqlConditions = [];
245
		$params = [$userId];
246
247
		if (!empty($name)) {
248
			$sqlConditions[] = 'LOWER(`title`) = LOWER(?)';
249
			$params[] = $name;
250
		}
251
252
		if (!empty($artistName)) {
253
			$sqlConditions[] = 'LOWER(`artist`.`name`) = LOWER(?)';
254
			$params[] = $artistName;
255
		}
256
257
		if (!empty($albumName)) {
258
			$sqlConditions[] = 'LOWER(`album`.`name`) = LOWER(?)';
259
			$params[] = $albumName;
260
		}
261
262
		// at least one condition has to be given, otherwise return an empty set
263
		if (\count($sqlConditions) > 0) {
264
			$sql = $this->selectUserEntities(\implode(' AND ', $sqlConditions));
265
			return $this->findEntities($sql, $params);
266
		} else {
267
			return [];
268
		}
269
	}
270
271
	const FAVORITE_TRACK = 0x1;
272
	const FAVORITE_ALBUM = 0x2;
273
	const FAVORITE_ARTIST = 0x4;
274
275
	/**
276
	 * Returns all tracks specified by various criteria, all of which are optional
277
	 * @param int[] $genres Array of genre IDs
278
	 * @param int[] $artists Array of artist IDs
279
	 * @param int|null $fromYear Earliest release year to include
280
	 * @param int|null $toYear Latest release year to include
281
	 * @param int|null $favorite Bit mask of FAVORITE_TRACK, FAVORITE_ALBUM, FAVORITE_ARTIST (given favorite types are ORed in the query)
282
	 * @param int $sortBy Sorting rule as defined in the class SortBy
283
	 * @param string $userId the name of the user
284
	 * @return Track[] Tracks matching the criteria
285
	 */
286
	public function findAllByCriteria(
287
			array $genres, array $artists, ?int $fromYear, ?int $toYear, ?int $favorite,
288
			int $sortBy, bool $invertSort, string $userId, ?int $limit=null, ?int $offset=null) : array {
289
290
		$sqlConditions = [];
291
		$params = [$userId];
292
293
		if (!empty($genres)) {
294
			$sqlConditions[] = '`genre_id` IN ' . $this->questionMarks(\count($genres));
295
			$params = \array_merge($params, $genres);
296
		}
297
298
		if (!empty($artists)) {
299
			$sqlConditions[] = '`artist_id` IN ' . $this->questionMarks(\count($artists));
300
			$params = \array_merge($params, $artists);
301
		}
302
303
		if (!empty($fromYear)) {
304
			$sqlConditions[] = '`year` >= ?';
305
			$params[] = $fromYear;
306
		}
307
308
		if (!empty($toYear)) {
309
			$sqlConditions[] = '`year` <= ?';
310
			$params[] = $toYear;
311
		}
312
313
		if (!empty($favorite)) {
314
			$favConds = [];
315
			if ($favorite & self::FAVORITE_TRACK) {
316
				$favConds[] = '`*PREFIX*music_tracks`.`starred` IS NOT NULL';
317
			}
318
			if ($favorite & self::FAVORITE_ALBUM) {
319
				$favConds[] = '`album`.`starred` IS NOT NULL';
320
			}
321
			if ($favorite & self::FAVORITE_ARTIST) {
322
				$favConds[] = '`artist`.`starred` IS NOT NULL';
323
			}
324
			$sqlConditions[] = '(' . \implode(' OR ', $favConds) . ')';
325
		} 
326
327
		$sql = $this->selectUserEntities(\implode(' AND ', $sqlConditions), $this->formatSortingClause($sortBy, $invertSort));
328
		return $this->findEntities($sql, $params, $limit, $offset);
329
	}
330
331
	/**
332
	 * Find most frequently played tracks
333
	 * @return Track[]
334
	 */
335
	public function findFrequentPlay(string $userId, ?int $limit=null, ?int $offset=null) : array {
336
		$sql = $this->selectUserEntities('`play_count` > 0', 'ORDER BY `play_count` DESC, LOWER(`title`)');
337
		return $this->findEntities($sql, [$userId], $limit, $offset);
338
	}
339
340
	/**
341
	 * Find most recently played tracks
342
	 * @return Track[]
343
	 */
344
	public function findRecentPlay(string $userId, ?int $limit=null, ?int $offset=null) : array {
345
		$sql = $this->selectUserEntities('`last_played` IS NOT NULL', 'ORDER BY `last_played` DESC');
346
		return $this->findEntities($sql, [$userId], $limit, $offset);
347
	}
348
349
	/**
350
	 * Find least recently played tracks
351
	 * @return Track[]
352
	 */
353
	public function findNotRecentPlay(string $userId, ?int $limit=null, ?int $offset=null) : array {
354
		$sql = $this->selectUserEntities(null, 'ORDER BY `last_played` ASC');
355
		return $this->findEntities($sql, [$userId], $limit, $offset);
356
	}
357
358
	/**
359
	 * Finds all track IDs of the user along with the parent folder ID of each track
360
	 * @return array where keys are folder IDs and values are arrays of track IDs
361
	 */
362
	public function findTrackAndFolderIds(string $userId) : array {
363
		$sql = 'SELECT `track`.`id` AS id, `file`.`name` AS `filename`, `file`.`parent` AS parent
364
				FROM `*PREFIX*music_tracks` `track`
365
				JOIN `*PREFIX*filecache` `file`
366
				ON `track`.`file_id` = `file`.`fileid`
367
				WHERE `track`.`user_id` = ?';
368
369
		$rows = $this->execute($sql, [$userId])->fetchAll();
370
371
		// Sort the results according the file names. This can't be made using ORDERBY in the
372
		// SQL query because then we couldn't use the "natural order" comparison algorithm
373
		\usort($rows, function ($a, $b) {
374
			return \strnatcasecmp($a['filename'], $b['filename']);
375
		});
376
377
		// group the files to parent folder "buckets"
378
		$result = [];
379
		foreach ($rows as $row) {
380
			$result[(int)$row['parent']][] = (int)$row['id'];
381
		}
382
383
		return $result;
384
	}
385
386
	/**
387
	 * Find names and parents of the file system nodes with given IDs within the given storage
388
	 * @param int[] $nodeIds
389
	 * @param string $storageId
390
	 * @return array where keys are the node IDs and values are associative arrays
391
	 *         like { 'name' => string, 'parent' => int };
392
	 */
393
	public function findNodeNamesAndParents(array $nodeIds, string $storageId) : array {
394
		$result = [];
395
396
		if (!empty($nodeIds)) {
397
			$sql = 'SELECT `fileid`, `name`, `parent` '.
398
					'FROM `*PREFIX*filecache` `filecache` '.
399
					'JOIN `*PREFIX*storages` `storages` '.
400
					'ON `filecache`.`storage` = `storages`.`numeric_id` '.
401
					'WHERE `storages`.`id` = ? '.
402
					'AND `filecache`.`fileid` IN '. $this->questionMarks(\count($nodeIds));
403
404
			$rows = $this->execute($sql, \array_merge([$storageId], $nodeIds))->fetchAll();
405
406
			foreach ($rows as $row) {
407
				$result[$row['fileid']] = [
408
					'name' => $row['name'],
409
					'parent' => (int)$row['parent']
410
				];
411
			}
412
		}
413
414
		return $result;
415
	}
416
417
	/**
418
	 * Returns all genre IDs associated with the given artist
419
	 * @return int[]
420
	 */
421
	public function getGenresByArtistId(int $artistId, string $userId) : array {
422
		$sql = 'SELECT DISTINCT(`genre_id`) FROM `*PREFIX*music_tracks` WHERE
423
				`genre_id` IS NOT NULL AND `user_id` = ? AND `artist_id` = ?';
424
		$rows = $this->execute($sql, [$userId, $artistId]);
425
		return $rows->fetchAll(\PDO::FETCH_COLUMN);
426
	}
427
428
	/**
429
	 * Returns all tracks IDs of the user, organized by the genre_id.
430
	 * @return array where keys are genre IDs and values are arrays of track IDs
431
	 */
432
	public function mapGenreIdsToTrackIds(string $userId) : array {
433
		$sql = 'SELECT `id`, `genre_id` FROM `*PREFIX*music_tracks`
434
				WHERE `genre_id` IS NOT NULL and `user_id` = ?';
435
		$rows = $this->execute($sql, [$userId])->fetchAll();
436
437
		$result = [];
438
		foreach ($rows as $row) {
439
			$result[(int)$row['genre_id']][] = (int)$row['id'];
440
		}
441
442
		return $result;
443
	}
444
445
	/**
446
	 * Returns file IDs of the tracks which do not have genre scanned. This is not the same
447
	 * thing as unknown genre, which means that the genre has been scanned but was not found
448
	 * from the track metadata.
449
	 * @return int[]
450
	 */
451
	public function findFilesWithoutScannedGenre(string $userId) : array {
452
		$sql = 'SELECT `track`.`file_id` FROM `*PREFIX*music_tracks` `track`
453
				INNER JOIN `*PREFIX*filecache` `file`
454
				ON `track`.`file_id` = `file`.`fileid`
455
				WHERE `genre_id` IS NULL and `user_id` = ?';
456
		$rows = $this->execute($sql, [$userId]);
457
		return $rows->fetchAll(\PDO::FETCH_COLUMN);
458
	}
459
460
	/**
461
	 * Update "last played" timestamp and increment the total play count of the track.
462
	 * The DB row is updated *without* updating the `updated` column.
463
	 * @return bool true if the track was found and updated, false otherwise
464
	 */
465
	public function recordTrackPlayed(int $trackId, string $userId, \DateTime $timeOfPlay) : bool {
466
		$sql = 'UPDATE `*PREFIX*music_tracks`
467
				SET `last_played` = ?, `play_count` = `play_count` + 1
468
				WHERE `user_id` = ? AND `id` = ?';
469
		$params = [$timeOfPlay->format(BaseMapper::SQL_DATE_FORMAT), $userId, $trackId];
470
		$result = $this->execute($sql, $params);
471
		return ($result->rowCount() > 0);
472
	}
473
474
	/**
475
	 * Overridden from the base implementation to provide support for table-specific rules
476
	 *
477
	 * {@inheritdoc}
478
	 * @see BaseMapper::advFormatSqlCondition()
479
	 */
480
	protected function advFormatSqlCondition(string $rule, string $sqlOp, string $conv) : string {
481
		// The extra subquery "mysqlhack" seen around some nested queries is needed in order for these to not be insanely slow on MySQL.
482
		switch ($rule) {
483
			case 'anywhere':		return self::formatAdvSearchAnywhereCond($sqlOp, $conv); 
484
			case 'album':			return "$conv(`album`.`name`) $sqlOp $conv(?)";
485
			case 'artist':			return "$conv(`artist`.`name`) $sqlOp $conv(?)";
486
			case 'album_artist':	return "`album_id` IN (SELECT `al`.`id` from `*PREFIX*music_albums` `al` JOIN `*PREFIX*music_artists` `ar` ON `al`.`album_artist_id` = `ar`.`id` WHERE $conv(`ar`.`name`) $sqlOp $conv(?))";
487
			case 'track':			return "`number` $sqlOp ?";
488
			case 'year':			return "`year` $sqlOp ?";
489
			case 'albumrating':		return "`album`.`rating` $sqlOp ?";
490
			case 'artistrating':	return "`artist`.`rating` $sqlOp ?";
491
			case 'favorite_album':	return "$conv(`album`.`name`) $sqlOp $conv(?) AND `album`.`starred` IS NOT NULL";
492
			case 'favorite_artist':	return "$conv(`artist`.`name`) $sqlOp $conv(?) AND `artist`.`starred` IS NOT NULL";
493
			case 'played_times':	return "`play_count` $sqlOp ?";
494
			case 'last_play':		return "`last_played` $sqlOp ?";
495
			case 'played':			// fall through, we give no access to other people's data
496
			case 'myplayed':		return "`last_played` $sqlOp"; // operator "IS NULL" or "IS NOT NULL"
497
			case 'myplayedalbum':	return "`album_id` IN (SELECT * FROM (SELECT `album_id` from `*PREFIX*music_tracks` GROUP BY `album_id` HAVING MAX(`last_played`) $sqlOp) mysqlhack)"; // operator "IS NULL" or "IS NOT NULL"
498
			case 'myplayedartist':	return "`artist_id` IN (SELECT * FROM (SELECT `artist_id` from `*PREFIX*music_tracks` GROUP BY `artist_id` HAVING MAX(`last_played`) $sqlOp) mysqlhack)"; // operator "IS NULL" or "IS NOT NULL"
499
			case 'time':			return "`length` $sqlOp ?";
500
			case 'bitrate':			return "`bitrate` $sqlOp ?";
501
			case 'genre':			// fall through
502
			case 'song_genre':		return "$conv(`genre`.`name`) $sqlOp $conv(?)";
503
			case 'album_genre':		return "`album_id` IN (SELECT * FROM (SELECT `album_id` FROM `*PREFIX*music_tracks` `t` JOIN `*PREFIX*music_genres` `g` ON `t`.`genre_id` = `g`.`id` GROUP BY `album_id` HAVING $conv(" . $this->sqlGroupConcat('`g`.`name`') . ") $sqlOp $conv(?)) mysqlhack)";
504
			case 'artist_genre':	return "`artist_id` IN (SELECT * FROM (SELECT `artist_id` FROM `*PREFIX*music_tracks` `t` JOIN `*PREFIX*music_genres` `g` ON `t`.`genre_id` = `g`.`id` GROUP BY `artist_id` HAVING $conv(" . $this->sqlGroupConcat('`g`.`name`') . ") $sqlOp $conv(?)) mysqlhack)";
505
			case 'no_genre':		return ($sqlOp == 'IS NOT NULL') ? '`genre`.`name` = ""' : '`genre`.`name` != ""';
506
			case 'playlist':		return "$sqlOp EXISTS (SELECT 1 from `*PREFIX*music_playlists` `p` WHERE `p`.`id` = ? AND `p`.`track_ids` LIKE " . $this->sqlConcat("'%|'", "`*PREFIX*music_tracks`.`id`", "'|%'") . ')';
507
			case 'playlist_name':	return "EXISTS (SELECT 1 from `*PREFIX*music_playlists` `p` WHERE $conv(`p`.`name`) $sqlOp $conv(?) AND `p`.`track_ids` LIKE " . $this->sqlConcat("'%|'", "`*PREFIX*music_tracks`.`id`", "'|%'") . ')';
508
			case 'recent_played':	return "`*PREFIX*music_tracks`.`id` IN (SELECT * FROM (SELECT `id` FROM `*PREFIX*music_tracks` WHERE `user_id` = ? ORDER BY `last_played` DESC LIMIT $sqlOp) mysqlhack)";
509
			case 'file':			return "$conv(`file`.`name`) $sqlOp $conv(?)";
510
			case 'mbid_song':		return parent::advFormatSqlCondition('mbid', $sqlOp, $conv); // alias
511
			case 'mbid_album':		return "`album`.`mbid` $sqlOp ?";
512
			case 'mbid_artist':		return "`artist`.`mbid` $sqlOp ?";
513
			default:				return parent::advFormatSqlCondition($rule, $sqlOp, $conv);
514
		}
515
	}
516
517
	private static function formatAdvSearchAnywhereCond(string $sqlOp, string $conv) : string {
518
		$fields = [
519
			"`*PREFIX*music_tracks`.`title`",
520
			"`file`.`name`",
521
			"`artist`.`name`",
522
			"`album`.`name`",
523
			"`genre`.`name`"
524
		];
525
		$parts = \array_map(function(string $field) use ($sqlOp, $conv) {
526
			return "$conv($field) $sqlOp $conv(?)";
527
		}, $fields);
528
529
		$negativeOp = \in_array($sqlOp, ['NOT LIKE', '!=', 'NOT REGEXP']);
530
		$cond = \implode($negativeOp ? ' AND ' : ' OR ', $parts);
531
532
		return "($cond)";
533
	}
534
535
	/**
536
	 * {@inheritdoc}
537
	 * @see \OCA\Music\Db\BaseMapper::findUniqueEntity()
538
	 * @param Track $track
539
	 * @return Track
540
	 */
541
	protected function findUniqueEntity(Entity $track) : Entity {
542
		return $this->findByFileId($track->getFileId(), $track->getUserId());
0 ignored issues
show
Bug introduced by
The method getFileId() does not exist on OCA\Music\Db\Entity. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

542
		return $this->findByFileId($track->/** @scrutinizer ignore-call */ getFileId(), $track->getUserId());
Loading history...
Bug introduced by
It seems like $track->getFileId() can also be of type null; however, parameter $fileId of OCA\Music\Db\TrackMapper::findByFileId() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

542
		return $this->findByFileId(/** @scrutinizer ignore-type */ $track->getFileId(), $track->getUserId());
Loading history...
543
	}
544
}
545