Passed
Pull Request — master (#1078)
by Pauli
05:55 queued 02:58
created

TrackMapper::advFormatSqlCondition()   D

Complexity

Conditions 31
Paths 31

Size

Total Lines 33
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 8
Bugs 0 Features 0
Metric Value
cc 31
eloc 31
nc 31
nop 2
dl 0
loc 33
rs 4.1666
c 8
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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

515
		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

515
		return $this->findByFileId(/** @scrutinizer ignore-type */ $track->getFileId(), $track->getUserId());
Loading history...
516
	}
517
}
518