Passed
Push — feature/909_Ampache_API_improv... ( 2004a5...b752cc )
by Pauli
12:33
created

BusinessLayer::delete()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 2
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
/**
3
 * ownCloud
4
 *
5
 * This file is licensed under the Affero General Public License version 3 or
6
 * later. See the COPYING file.
7
 *
8
 * @author Alessandro Cosentino <[email protected]>
9
 * @author Bernhard Posselt <[email protected]>
10
 * @author Pauli Järvinen <[email protected]>
11
 * @copyright Alessandro Cosentino 2012
12
 * @copyright Bernhard Posselt 2012, 2014
13
 * @copyright Pauli Järvinen 2017 - 2023
14
 */
15
16
namespace OCA\Music\AppFramework\BusinessLayer;
17
18
use OCA\Music\Db\BaseMapper;
19
use OCA\Music\Db\Entity;
20
use OCA\Music\Db\MatchMode;
21
use OCA\Music\Db\SortBy;
22
use OCA\Music\Utility\Util;
23
24
use OCP\AppFramework\Db\DoesNotExistException;
25
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
26
use OCP\IL10N;
27
28
/**
29
 * @phpstan-template EntityType of Entity
30
 */
31
abstract class BusinessLayer {
32
	protected $mapper;
33
34
	// Some SQLite installations can't handle more than 999 query args. Remember that `user_id` takes one slot in most queries.
35
	public const MAX_SQL_ARGS = 999;
36
37
	/**
38
	 * @phpstan-param BaseMapper<EntityType> $mapper
39
	 */
40
	public function __construct(BaseMapper $mapper) {
41
		$this->mapper = $mapper;
42
	}
43
44
	/**
45
	 * Update an entity in the database
46
	 * @phpstan-param EntityType $entity
47
	 * @phpstan-return EntityType
48
	 */
49
	public function update(Entity $entity) : Entity {
50
		return $this->mapper->update($entity);
51
	}
52
53
	/**
54
	 * Delete an entity
55
	 * @param int $id the id of the entity
56
	 * @param string $userId the name of the user for security reasons
57
	 * @throws BusinessLayerException if the entity does not exist or more than one entity exists
58
	 * @phpstan-return EntityType
59
	 */
60
	public function delete(int $id, string $userId) : Entity {
61
		$entity = $this->find($id, $userId);
62
		return $this->mapper->delete($entity);
63
	}
64
65
	/**
66
	 * Deletes entities without specifying the owning user.
67
	 * This should never be called directly from the HTML API, but only in case
68
	 * we can actually trust the passed IDs (e.g. file deleted hook).
69
	 * @param array $ids the ids of the entities which should be deleted
70
	 */
71
	public function deleteById(array $ids) : void {
72
		if (\count($ids) > 0) {
73
			$this->mapper->deleteById($ids);
74
		}
75
	}
76
77
	/**
78
	 * Delete all entities of the given user
79
	 */
80
	public function deleteAll(string $userId) : void {
81
		$this->mapper->deleteAll($userId);
82
	}
83
84
	/**
85
	 * Finds an entity by id
86
	 * @param int $id the id of the entity
87
	 * @param string $userId the name of the user for security reasons
88
	 * @throws BusinessLayerException if the entity does not exist or more than one entity exists
89
	 * @phpstan-return EntityType
90
	 */
91
	public function find(int $id, string $userId) : Entity {
92
		try {
93
			return $this->mapper->find($id, $userId);
94
		} catch (DoesNotExistException $ex) {
95
			throw new BusinessLayerException($ex->getMessage());
96
		} catch (MultipleObjectsReturnedException $ex) {
97
			throw new BusinessLayerException($ex->getMessage());
98
		}
99
	}
100
101
	/**
102
	 * Finds an entity by id, or returns an empty entity instance if the requested one is not found
103
	 * @param int $id the id of the entity
104
	 * @param string $userId the name of the user for security reasons
105
	 * @phpstan-return EntityType
106
	 */
107
	public function findOrDefault(int $id, string $userId) : Entity {
108
		try {
109
			return $this->find($id, $userId);
110
		} catch (BusinessLayerException $ex) {
111
			return $this->mapper->createEntity();
112
		}
113
	}
114
115
	/**
116
	 * Find all entities matching the given IDs.
117
	 * Specifying the user is optional; if omitted, the caller should make sure that
118
	 * user's data is not leaked to unauthorized users.
119
	 * @param integer[] $ids  IDs of the entities to be found
120
	 * @param string|null $userId
121
	 * @param bool $preserveOrder If true, then the result will be in the same order as @a $ids
122
	 * @return Entity[]
123
	 * @phpstan-return EntityType[]
124
	 */
125
	public function findById(array $ids, string $userId=null, bool $preserveOrder=false) : array {
126
		$entities = [];
127
		if (\count($ids) > 0) {
128
			// don't use more than 999 SQL args in one query since that may be a problem for SQLite
129
			$idChunks = \array_chunk($ids, 998);
130
			foreach ($idChunks as $idChunk) {
131
				$entities = \array_merge($entities, $this->mapper->findById($idChunk, $userId));
132
			}
133
		}
134
135
		if ($preserveOrder) {
136
			$lut = Util::createIdLookupTable($entities);
137
			$result = [];
138
			foreach ($ids as $id) {
139
				$result[] = $lut[$id];
140
			}
141
		} else {
142
			$result = $entities;
143
		}
144
145
		return $result;
146
	}
147
148
	/**
149
	 * Finds all entities
150
	 * @param string $userId the name of the user
151
	 * @param integer $sortBy sort order of the result set
152
	 * @param integer|null $limit
153
	 * @param integer|null $offset
154
	 * @param string|null $createdMin Optional minimum `created` timestamp.
155
	 * @param string|null $createdMax Optional maximum `created` timestamp.
156
	 * @param string|null $updatedMin Optional minimum `updated` timestamp.
157
	 * @param string|null $updatedMax Optional maximum `updated` timestamp.
158
	 * @return Entity[]
159
	 * @phpstan-return EntityType[]
160
	 */
161
	public function findAll(
162
			string $userId, int $sortBy=SortBy::None, ?int $limit=null, ?int $offset=null,
163
			?string $createdMin=null, ?string $createdMax=null, ?string $updatedMin=null, ?string $updatedMax=null) : array {
164
		return $this->mapper->findAll($userId, $sortBy, $limit, $offset, $createdMin, $createdMax, $updatedMin, $updatedMax);
165
	}
166
167
	/**
168
	 * Return all entities with name matching the search criteria
169
	 * @param string|null $createdMin Optional minimum `created` timestamp.
170
	 * @param string|null $createdMax Optional maximum `created` timestamp.
171
	 * @param string|null $updatedMin Optional minimum `updated` timestamp.
172
	 * @param string|null $updatedMax Optional maximum `updated` timestamp.
173
	 * @return Entity[]
174
	 * @phpstan-return EntityType[]
175
	 */
176
	public function findAllByName(
177
			?string $name, string $userId, int $matchMode=MatchMode::Exact, ?int $limit=null, ?int $offset=null,
178
			?string $createdMin=null, ?string $createdMax=null, ?string $updatedMin=null, ?string $updatedMax=null) : array {
179
		if ($name !== null) {
180
			$name = \trim($name);
181
		}
182
		return $this->mapper->findAllByName($name, $userId, $matchMode, $limit, $offset, $createdMin, $createdMax, $updatedMin, $updatedMax);
183
	}
184
185
	/**
186
	 * Find all starred entities
187
	 * @return Entity[]
188
	 * @phpstan-return EntityType[]
189
	 */
190
	public function findAllStarred(string $userId, ?int $limit=null, ?int $offset=null) : array {
191
		return $this->mapper->findAllStarred($userId, $limit, $offset);
192
	}
193
194
	/**
195
	 * Find all entities with user-given rating 1-5
196
	 * @return Entity[]
197
	 * @phpstan-return EntityType[]
198
	 */
199
	public function findAllRated(string $userId, ?int $limit=null, ?int $offset=null) : array {
200
		return $this->mapper->findAllRated($userId, $limit, $offset);
201
	}
202
203
	/**
204
	 * Find all entities matching multiple criteria, as needed for the Ampache API method `advanced_search`
205
	 * @param string $conjunction Operator to use between the rules, either 'and' or 'or'
206
	 * @param array $rules Array of arrays: [['rule' => string, 'operator' => string, 'input' => string], ...]
207
	 * 				Here, 'rule' has dozens of possible values depending on the business layer in question,
208
	 * 				(see https://ampache.org/api/api-advanced-search#available-search-rules, alias names not supported here),
209
	 * 				'operator' is one of 
210
	 * 				['contain', 'notcontain', 'start', 'end', 'is', 'isnot', '>=', '<=', '=', '!=', '>', '<', 'true', 'false', 'equal', 'ne', 'limit'],
211
	 * 				'input' is the right side value of the 'operator' (disregarded for the operators 'true' and 'false')
212
	 * @return Entity[]
213
	 * @phpstan-return EntityType[]
214
	 */
215
	public function findAllAdvanced(string $conjunction, array $rules, string $userId, ?int $limit=null, ?int $offset=null) : array {
216
		if ($conjunction !== 'and' && $conjunction !== 'or') {
217
			throw new BusinessLayerException("Bad conjunction '$conjunction'");
218
		}
219
		try {
220
			return $this->mapper->findAllAdvanced($conjunction, $rules, $userId, $limit, $offset);
221
		} catch (\Exception $e) {
222
			// catch everything as many kinds of DB exceptions are possible on various cloud versions
223
			throw new BusinessLayerException($e->getMessage());
224
		}
225
	}
226
227
	/**
228
	 * Find IDs of all user's entities of this kind.
229
	 * Optionally, limit to given IDs which may be used to check the validity of those IDs.
230
	 * @return int[]
231
	 */
232
	public function findAllIds(string $userId, ?array $ids = null) : array {
233
		if ($ids === null || \count($ids) > 0) {
234
			return $this->mapper->findAllIds($userId, $ids);
235
		} else {
236
			return [];
237
		}
238
	}
239
240
	/**
241
	 * Find all IDs and names of user's entities of this kind.
242
	 * Optionally, limit results based on a parent entity (not applicable for all entity types) or update/insert times or name
243
	 * @param bool $excludeChildless Exclude entities having no child-entities if applicable for this business layer (eg. artists without albums)
244
	 * @return array of arrays like ['id' => string, 'name' => string]
245
	 */
246
	public function findAllIdsAndNames(string $userId, IL10N $l10n, ?int $parentId=null, ?int $limit=null, ?int $offset=null,
247
			?string $createdMin=null, ?string $createdMax=null, ?string $updatedMin=null, ?string $updatedMax=null,
248
			bool $excludeChidless=false, ?string $name=null) : array {
249
		try {
250
			$idsAndNames = $this->mapper->findAllIdsAndNames(
251
				$userId, $parentId, $limit, $offset, $createdMin, $createdMax, $updatedMin, $updatedMax, $excludeChidless, $name);
252
		} catch (\DomainException $ex) {
253
			throw new BusinessLayerException($ex->getMessage());
254
		}
255
		foreach ($idsAndNames as &$idAndName) {
256
			if (empty($idAndName['name'])) {
257
				$emptyEntity = $this->mapper->createEntity($idAndName);
0 ignored issues
show
Unused Code introduced by
The call to OCA\Music\Db\BaseMapper::createEntity() has too many arguments starting with $idAndName. ( Ignorable by Annotation )

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

257
				/** @scrutinizer ignore-call */ 
258
    $emptyEntity = $this->mapper->createEntity($idAndName);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
258
				$idAndName['name'] = $emptyEntity->getNameString($l10n);
259
			}
260
		}
261
		return $idsAndNames;
262
	}
263
264
	/**
265
	 * Find IDs of all users owning any entities of this business layer
266
	 * @return string[]
267
	 */
268
	public function findAllUsers() : array {
269
		return $this->mapper->findAllUsers();
270
	}
271
272
	/**
273
	 * Set the given entities as "starred" on this date
274
	 * @param int[] $ids
275
	 * @param string $userId
276
	 * @return int number of modified entities
277
	 */
278
	public function setStarred(array $ids, string $userId) : int {
279
		if (\count($ids) > 0) {
280
			return $this->mapper->setStarredDate(new \DateTime(), $ids, $userId);
281
		} else {
282
			return 0;
283
		}
284
	}
285
286
	/**
287
	 * Remove the "starred" status of the given entities
288
	 * @param integer[] $ids
289
	 * @param string $userId
290
	 * @return int number of modified entities
291
	 */
292
	public function unsetStarred(array $ids, string $userId) : int {
293
		if (\count($ids) > 0) {
294
			return $this->mapper->setStarredDate(null, $ids, $userId);
295
		} else {
296
			return 0;
297
		}
298
	}
299
300
	/**
301
	 * Tests if entity with given ID and user ID exists in the database
302
	 * @param int $id
303
	 * @param string $userId
304
	 * @return bool
305
	 */
306
	public function exists(int $id, string $userId) : bool {
307
		return $this->mapper->exists($id, $userId);
308
	}
309
310
	/**
311
	 * Get the number of entities
312
	 * @param string $userId
313
	 */
314
	public function count(string $userId) : int {
315
		return $this->mapper->count($userId);
316
	}
317
318
	/**
319
	 * Get the timestamp of the latest insert operation on the entity type in question
320
	 */
321
	public function latestInsertTime(string $userId) : \DateTime {
322
		return $this->mapper->latestInsertTime($userId) ?? new \DateTime('1970-01-01');
323
	}
324
325
	/**
326
	 * Get the timestamp of the latest update operation on the entity type in question
327
	 */
328
	public function latestUpdateTime(string $userId) : \DateTime {
329
		return $this->mapper->latestUpdateTime($userId) ?? new \DateTime('1970-01-01');
330
	}
331
}
332