Passed
Push — master ( c7cb4e...ce9a66 )
by Pauli
03:03
created

TrackMapper::findAllByCriteria()   B

Complexity

Conditions 9
Paths 144

Size

Total Lines 43
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 25
c 1
b 0
f 0
nc 144
nop 10
dl 0
loc 43
rs 7.6888

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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
	 * @return int[]
124
	 */
125
	public function findAllFileIds(string $userId) : array {
126
		$sql = 'SELECT `file_id` FROM `*PREFIX*music_tracks` WHERE `user_id` = ?';
127
		$result = $this->execute($sql, [$userId]);
128
		return $result->fetchAll(\PDO::FETCH_COLUMN);
129
	}
130
131
	/**
132
	 * Find a track of user matching a file ID
133
	 * @throws \OCP\AppFramework\Db\DoesNotExistException if not found
134
	 */
135
	public function findByFileId(int $fileId, string $userId) : Track {
136
		$sql = $this->selectUserEntities('`file_id` = ?');
137
		$params = [$userId, $fileId];
138
		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...
139
	}
140
141
	/**
142
	 * Find tracks of user with multiple file IDs
143
	 * @param integer[] $fileIds
144
	 * @param string[] $userIds
145
	 * @return Track[]
146
	 */
147
	public function findByFileIds(array $fileIds, array $userIds) : array {
148
		$sql = $this->selectEntities(
149
				'`*PREFIX*music_tracks`.`user_id` IN ' . $this->questionMarks(\count($userIds)) .
150
				' AND `file_id` IN '. $this->questionMarks(\count($fileIds)));
151
		$params = \array_merge($userIds, $fileIds);
152
		return $this->findEntities($sql, $params);
153
	}
154
155
	/**
156
	 * Finds tracks of all users matching one or multiple file IDs
157
	 * @param integer[] $fileIds
158
	 * @return Track[]
159
	 */
160
	public function findAllByFileIds(array $fileIds) : array {
161
		$sql = $this->selectEntities('`file_id` IN '.
162
				$this->questionMarks(\count($fileIds)));
163
		return $this->findEntities($sql, $fileIds);
164
	}
165
166
	public function countByArtist(int $artistId) : int {
167
		$sql = 'SELECT COUNT(*) AS `count` FROM `*PREFIX*music_tracks` WHERE `artist_id` = ?';
168
		$result = $this->execute($sql, [$artistId]);
169
		$row = $result->fetch();
170
		return (int)$row['count'];
171
	}
172
173
	public function countByAlbum(int $albumId) : int {
174
		$sql = 'SELECT COUNT(*) AS `count` FROM `*PREFIX*music_tracks` WHERE `album_id` = ?';
175
		$result = $this->execute($sql, [$albumId]);
176
		$row = $result->fetch();
177
		return (int)$row['count'];
178
	}
179
180
	/**
181
	 * @return integer Duration in seconds
182
	 */
183
	public function totalDurationOfAlbum(int $albumId) : int {
184
		$sql = 'SELECT SUM(`length`) AS `duration` FROM `*PREFIX*music_tracks` WHERE `album_id` = ?';
185
		$result = $this->execute($sql, [$albumId]);
186
		$row = $result->fetch();
187
		return (int)$row['duration'];
188
	}
189
190
	/**
191
	 * @return integer Duration in seconds
192
	 */
193
	public function totalDurationByArtist(int $artistId) : int {
194
		$sql = 'SELECT SUM(`length`) AS `duration` FROM `*PREFIX*music_tracks` WHERE `artist_id` = ?';
195
		$result = $this->execute($sql, [$artistId]);
196
		$row = $result->fetch();
197
		return (int)$row['duration'];
198
	}
199
200
	/**
201
	 * Get durations of the given tracks.
202
	 * @param integer[] $trackIds
203
	 * @return array {int => int} where keys are track IDs and values are corresponding durations
204
	 */
205
	public function getDurations(array $trackIds) : array {
206
		$result = [];
207
208
		if (!empty($trackIds)) {
209
			$sql = 'SELECT `id`, `length` FROM `*PREFIX*music_tracks` WHERE `id` IN ' .
210
						$this->questionMarks(\count($trackIds));
211
			$rows = $this->execute($sql, $trackIds)->fetchAll();
212
			foreach ($rows as $row) {
213
				$result[$row['id']] = (int)$row['length'];
214
			}
215
		}
216
		return $result;
217
	}
218
219
	/**
220
	 * @return Track[]
221
	 */
222
	public function findAllByNameRecursive(string $name, string $userId, ?int $limit=null, ?int $offset=null) {
223
		$condition = '( LOWER(`artist`.`name`) LIKE LOWER(?) OR
224
						LOWER(`album`.`name`) LIKE LOWER(?) OR
225
						LOWER(`title`) LIKE LOWER(?) )';
226
		$sql = $this->selectUserEntities($condition, 'ORDER BY LOWER(`title`)');
227
		$name = BaseMapper::prepareSubstringSearchPattern($name);
228
		$params = [$userId, $name, $name, $name];
229
		return $this->findEntities($sql, $params, $limit, $offset);
230
	}
231
232
	/**
233
	 * Returns all tracks specified by name, artist name, and/or album name
234
	 * @param string|null $name the name of the track
235
	 * @param string|null $artistName the name of the artist
236
	 * @param string $userId the name of the user
237
	 * @return Track[] Tracks matching the criteria
238
	 */
239
	public function findAllByNameArtistOrAlbum(?string $name, ?string $artistName, ?string $albumName, string $userId) : array {
240
		$sqlConditions = [];
241
		$params = [$userId];
242
243
		if (!empty($name)) {
244
			$sqlConditions[] = 'LOWER(`title`) = LOWER(?)';
245
			$params[] = $name;
246
		}
247
248
		if (!empty($artistName)) {
249
			$sqlConditions[] = 'LOWER(`artist`.`name`) = LOWER(?)';
250
			$params[] = $artistName;
251
		}
252
253
		if (!empty($albumName)) {
254
			$sqlConditions[] = 'LOWER(`album`.`name`) = LOWER(?)';
255
			$params[] = $albumName;
256
		}
257
258
		// at least one condition has to be given, otherwise return an empty set
259
		if (\count($sqlConditions) > 0) {
260
			$sql = $this->selectUserEntities(\implode(' AND ', $sqlConditions));
261
			return $this->findEntities($sql, $params);
262
		} else {
263
			return [];
264
		}
265
	}
266
267
	const FAVORITE_TRACK = 0x1;
268
	const FAVORITE_ALBUM = 0x2;
269
	const FAVORITE_ARTIST = 0x4;
270
271
	/**
272
	 * Returns all tracks specified by various criteria, all of which are optional
273
	 * @param int[] $genres Array of genre IDs
274
	 * @param int[] $artists Array of artist IDs
275
	 * @param int|null $fromYear Earliest release year to include
276
	 * @param int|null $toYear Latest release year to include
277
	 * @param int|null $favorite Bit mask of FAVORITE_TRACK, FAVORITE_ALBUM, FAVORITE_ARTIST (given favorite types are ORed in the query)
278
	 * @param int $sortBy Sorting rule as defined in the class SortBy
279
	 * @param string $userId the name of the user
280
	 * @return Track[] Tracks matching the criteria
281
	 */
282
	public function findAllByCriteria(
283
			array $genres, array $artists, ?int $fromYear, ?int $toYear, ?int $favorite,
284
			int $sortBy, bool $invertSort, string $userId, ?int $limit=null, ?int $offset=null) : array {
285
286
		$sqlConditions = [];
287
		$params = [$userId];
288
289
		if (!empty($genres)) {
290
			$sqlConditions[] = '`genre_id` IN ' . $this->questionMarks(\count($genres));
291
			$params = \array_merge($params, $genres);
292
		}
293
294
		if (!empty($artists)) {
295
			$sqlConditions[] = '`artist_id` IN ' . $this->questionMarks(\count($artists));
296
			$params = \array_merge($params, $artists);
297
		}
298
299
		if (!empty($fromYear)) {
300
			$sqlConditions[] = '`year` >= ?';
301
			$params[] = $fromYear;
302
		}
303
304
		if (!empty($toYear)) {
305
			$sqlConditions[] = '`year` <= ?';
306
			$params[] = $toYear;
307
		}
308
309
		if (!empty($favorite)) {
310
			$favConds = [];
311
			if ($favorite & self::FAVORITE_TRACK) {
312
				$favConds[] = '`*PREFIX*music_tracks`.`starred` IS NOT NULL';
313
			}
314
			if ($favorite & self::FAVORITE_ALBUM) {
315
				$favConds[] = '`album`.`starred` IS NOT NULL';
316
			}
317
			if ($favorite & self::FAVORITE_ARTIST) {
318
				$favConds[] = '`artist`.`starred` IS NOT NULL';
319
			}
320
			$sqlConditions[] = '(' . \implode(' OR ', $favConds) . ')';
321
		} 
322
323
		$sql = $this->selectUserEntities(\implode(' AND ', $sqlConditions), $this->formatSortingClause($sortBy, $invertSort));
324
		return $this->findEntities($sql, $params, $limit, $offset);
325
	}
326
327
	/**
328
	 * Find most frequently played tracks
329
	 * @return Track[]
330
	 */
331
	public function findFrequentPlay(string $userId, ?int $limit=null, ?int $offset=null) : array {
332
		$sql = $this->selectUserEntities('`play_count` > 0', 'ORDER BY `play_count` DESC, LOWER(`title`)');
333
		return $this->findEntities($sql, [$userId], $limit, $offset);
334
	}
335
336
	/**
337
	 * Find most recently played tracks
338
	 * @return Track[]
339
	 */
340
	public function findRecentPlay(string $userId, ?int $limit=null, ?int $offset=null) : array {
341
		$sql = $this->selectUserEntities('`last_played` IS NOT NULL', 'ORDER BY `last_played` DESC');
342
		return $this->findEntities($sql, [$userId], $limit, $offset);
343
	}
344
345
	/**
346
	 * Find least recently played tracks
347
	 * @return Track[]
348
	 */
349
	public function findNotRecentPlay(string $userId, ?int $limit=null, ?int $offset=null) : array {
350
		$sql = $this->selectUserEntities(null, 'ORDER BY `last_played` ASC');
351
		return $this->findEntities($sql, [$userId], $limit, $offset);
352
	}
353
354
	/**
355
	 * Finds all track IDs of the user along with the parent folder ID of each track
356
	 * @return array where keys are folder IDs and values are arrays of track IDs
357
	 */
358
	public function findTrackAndFolderIds(string $userId) : array {
359
		$sql = 'SELECT `track`.`id` AS id, `file`.`name` AS `filename`, `file`.`parent` AS parent
360
				FROM `*PREFIX*music_tracks` `track`
361
				JOIN `*PREFIX*filecache` `file`
362
				ON `track`.`file_id` = `file`.`fileid`
363
				WHERE `track`.`user_id` = ?';
364
365
		$rows = $this->execute($sql, [$userId])->fetchAll();
366
367
		// Sort the results according the file names. This can't be made using ORDERBY in the
368
		// SQL query because then we couldn't use the "natural order" comparison algorithm
369
		\usort($rows, function ($a, $b) {
370
			return \strnatcasecmp($a['filename'], $b['filename']);
371
		});
372
373
		// group the files to parent folder "buckets"
374
		$result = [];
375
		foreach ($rows as $row) {
376
			$result[(int)$row['parent']][] = (int)$row['id'];
377
		}
378
379
		return $result;
380
	}
381
382
	/**
383
	 * Find names and parents of the file system nodes with given IDs within the given storage
384
	 * @param int[] $nodeIds
385
	 * @param string $storageId
386
	 * @return array where keys are the node IDs and values are associative arrays
387
	 *         like { 'name' => string, 'parent' => int };
388
	 */
389
	public function findNodeNamesAndParents(array $nodeIds, string $storageId) : array {
390
		$result = [];
391
392
		if (!empty($nodeIds)) {
393
			$sql = 'SELECT `fileid`, `name`, `parent` '.
394
					'FROM `*PREFIX*filecache` `filecache` '.
395
					'JOIN `*PREFIX*storages` `storages` '.
396
					'ON `filecache`.`storage` = `storages`.`numeric_id` '.
397
					'WHERE `storages`.`id` = ? '.
398
					'AND `filecache`.`fileid` IN '. $this->questionMarks(\count($nodeIds));
399
400
			$rows = $this->execute($sql, \array_merge([$storageId], $nodeIds))->fetchAll();
401
402
			foreach ($rows as $row) {
403
				$result[$row['fileid']] = [
404
					'name' => $row['name'],
405
					'parent' => (int)$row['parent']
406
				];
407
			}
408
		}
409
410
		return $result;
411
	}
412
413
	/**
414
	 * Returns all genre IDs associated with the given artist
415
	 * @return int[]
416
	 */
417
	public function getGenresByArtistId(int $artistId, string $userId) : array {
418
		$sql = 'SELECT DISTINCT(`genre_id`) FROM `*PREFIX*music_tracks` WHERE
419
				`genre_id` IS NOT NULL AND `user_id` = ? AND `artist_id` = ?';
420
		$rows = $this->execute($sql, [$userId, $artistId]);
421
		return $rows->fetchAll(\PDO::FETCH_COLUMN);
422
	}
423
424
	/**
425
	 * Returns all tracks IDs of the user, organized by the genre_id.
426
	 * @return array where keys are genre IDs and values are arrays of track IDs
427
	 */
428
	public function mapGenreIdsToTrackIds(string $userId) : array {
429
		$sql = 'SELECT `id`, `genre_id` FROM `*PREFIX*music_tracks`
430
				WHERE `genre_id` IS NOT NULL and `user_id` = ?';
431
		$rows = $this->execute($sql, [$userId])->fetchAll();
432
433
		$result = [];
434
		foreach ($rows as $row) {
435
			$result[(int)$row['genre_id']][] = (int)$row['id'];
436
		}
437
438
		return $result;
439
	}
440
441
	/**
442
	 * Returns file IDs of the tracks which do not have genre scanned. This is not the same
443
	 * thing as unknown genre, which means that the genre has been scanned but was not found
444
	 * from the track metadata.
445
	 * @return int[]
446
	 */
447
	public function findFilesWithoutScannedGenre(string $userId) : array {
448
		$sql = 'SELECT `track`.`file_id` FROM `*PREFIX*music_tracks` `track`
449
				INNER JOIN `*PREFIX*filecache` `file`
450
				ON `track`.`file_id` = `file`.`fileid`
451
				WHERE `genre_id` IS NULL and `user_id` = ?';
452
		$rows = $this->execute($sql, [$userId]);
453
		return $rows->fetchAll(\PDO::FETCH_COLUMN);
454
	}
455
456
	/**
457
	 * Update "last played" timestamp and increment the total play count of the track.
458
	 * The DB row is updated *without* updating the `updated` column.
459
	 * @return bool true if the track was found and updated, false otherwise
460
	 */
461
	public function recordTrackPlayed(int $trackId, string $userId, \DateTime $timeOfPlay) : bool {
462
		$sql = 'UPDATE `*PREFIX*music_tracks`
463
				SET `last_played` = ?, `play_count` = `play_count` + 1
464
				WHERE `user_id` = ? AND `id` = ?';
465
		$params = [$timeOfPlay->format(BaseMapper::SQL_DATE_FORMAT), $userId, $trackId];
466
		$result = $this->execute($sql, $params);
467
		return ($result->rowCount() > 0);
468
	}
469
470
	/**
471
	 * Marks tracks as dirty, ultimately requesting the user to rescan them
472
	 * @param int[] $fileIds file IDs of the tracks to mark as dirty
473
	 * @param string[]|null $userIds the target users; if omitted, the tracks matching the
474
	 *                      $fileIds are marked for all users
475
	 * @return int number of rows affected
476
	 */
477
	public function markTracksDirty(array $fileIds, ?array $userIds=null) : int {
478
		$sql = 'UPDATE `*PREFIX*music_tracks`
479
				SET `dirty` = 1
480
				WHERE `file_id` IN ' . $this->questionMarks(\count($fileIds));
481
		$params = $fileIds;
482
483
		if (!empty($userIds)) {
484
			$sql .= ' AND `user_id` IN ' . $this->questionMarks(\count($userIds));
485
			$params = \array_merge($params, $userIds);
486
		}
487
488
		$result = $this->execute($sql, $params);
489
		return $result->rowCount();
490
	}
491
492
	/**
493
	 * Overridden from the base implementation to provide support for table-specific rules
494
	 *
495
	 * {@inheritdoc}
496
	 * @see BaseMapper::advFormatSqlCondition()
497
	 */
498
	protected function advFormatSqlCondition(string $rule, string $sqlOp, string $conv) : string {
499
		// The extra subquery "mysqlhack" seen around some nested queries is needed in order for these to not be insanely slow on MySQL.
500
		switch ($rule) {
501
			case 'anywhere':		return self::formatAdvSearchAnywhereCond($sqlOp, $conv); 
502
			case 'album':			return "$conv(`album`.`name`) $sqlOp $conv(?)";
503
			case 'artist':			return "$conv(`artist`.`name`) $sqlOp $conv(?)";
504
			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(?))";
505
			case 'album_artist_id':	return "$sqlOp `album_id` IN (SELECT `id` from `*PREFIX*music_albums` WHERE `album_artist_id` = ?)"; // our own API extension
506
			case 'track':			return "`number` $sqlOp ?";
507
			case 'year':			return "`year` $sqlOp ?";
508
			case 'albumrating':		return "`album`.`rating` $sqlOp ?";
509
			case 'artistrating':	return "`artist`.`rating` $sqlOp ?";
510
			case 'favorite_album':	return "$conv(`album`.`name`) $sqlOp $conv(?) AND `album`.`starred` IS NOT NULL";
511
			case 'favorite_artist':	return "$conv(`artist`.`name`) $sqlOp $conv(?) AND `artist`.`starred` IS NOT NULL";
512
			case 'played_times':	return "`play_count` $sqlOp ?";
513
			case 'last_play':		return "`last_played` $sqlOp ?";
514
			case 'played':			// fall through, we give no access to other people's data
515
			case 'myplayed':		return "`last_played` $sqlOp"; // operator "IS NULL" or "IS NOT NULL"
516
			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"
517
			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"
518
			case 'time':			return "`length` $sqlOp ?";
519
			case 'bitrate':			return "`bitrate` $sqlOp ?";
520
			case 'genre':			// fall through
521
			case 'song_genre':		return "$conv(`genre`.`name`) $sqlOp $conv(?)";
522
			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)";
523
			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)";
524
			case 'no_genre':		return ($sqlOp == 'IS NOT NULL') ? '`genre`.`name` = ""' : '`genre`.`name` != ""';
525
			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`", "'|%'") . ')';
526
			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`", "'|%'") . ')';
527
			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)";
528
			case 'file':			return "$conv(`file`.`name`) $sqlOp $conv(?)";
529
			case 'mbid_song':		return parent::advFormatSqlCondition('mbid', $sqlOp, $conv); // alias
530
			case 'mbid_album':		return "`album`.`mbid` $sqlOp ?";
531
			case 'mbid_artist':		return "`artist`.`mbid` $sqlOp ?";
532
			default:				return parent::advFormatSqlCondition($rule, $sqlOp, $conv);
533
		}
534
	}
535
536
	private static function formatAdvSearchAnywhereCond(string $sqlOp, string $conv) : string {
537
		$fields = [
538
			"`*PREFIX*music_tracks`.`title`",
539
			"`file`.`name`",
540
			"`artist`.`name`",
541
			"`album`.`name`",
542
			"`genre`.`name`"
543
		];
544
		$parts = \array_map(function(string $field) use ($sqlOp, $conv) {
545
			return "$conv($field) $sqlOp $conv(?)";
546
		}, $fields);
547
548
		$negativeOp = \in_array($sqlOp, ['NOT LIKE', '!=', 'NOT REGEXP']);
549
		$cond = \implode($negativeOp ? ' AND ' : ' OR ', $parts);
550
551
		return "($cond)";
552
	}
553
554
	/**
555
	 * {@inheritdoc}
556
	 * @see \OCA\Music\Db\BaseMapper::findUniqueEntity()
557
	 * @param Track $track
558
	 * @return Track
559
	 */
560
	protected function findUniqueEntity(Entity $track) : Entity {
561
		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

561
		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

561
		return $this->findByFileId(/** @scrutinizer ignore-type */ $track->getFileId(), $track->getUserId());
Loading history...
562
	}
563
}
564