Passed
Push — master ( 26d767...2e2600 )
by Pauli
01:48
created

Util::extractIds()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 4
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
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 Pauli Järvinen <[email protected]>
10
 * @copyright Pauli Järvinen 2018 - 2020
11
 */
12
13
namespace OCA\Music\Utility;
14
15
use \OCP\Files\Folder;
16
17
/**
18
 * Miscellaneous static utility functions
19
 */
20
class Util {
21
22
	/**
23
	 * Extract ID of each array element by calling getId and return
24
	 * the IDs as an array
25
	 * @param array $arr
26
	 * @return array
27 4
	 */
28 4
	public static function extractIds(array $arr) {
29 4
		return \array_map(function ($i) {
30
			return $i->getId();
31
		}, $arr);
32
	}
33
34
	/**
35
	 * Extract User ID of each array element by calling getUserId and return
36
	 * the IDs as an array
37
	 * @param array $arr
38
	 * @return array
39
	 */
40
	public static function extractUserIds(array $arr) {
41
		return \array_map(function ($i) {
42
			return $i->getUserId();
43
		}, $arr);
44
	}
45
46
	/**
47
	 * Create look-up table from given array of items which have a `getId` function.
48
	 * @param array $array
49
	 * @return array where keys are the values returned by `getId` of each item
50
	 */
51
	public static function createIdLookupTable(array $array) {
52
		$lut = [];
53
		foreach ($array as $item) {
54
			$lut[$item->getId()] = $item;
55
		}
56
		return $lut;
57
	}
58
59
	/**
60
	 * Get difference of two arrays, i.e. elements belonging to $b but not $a.
61
	 * This function is faster than the built-in array_diff for large arrays but
62
	 * at the expense of higher RAM usage and can be used only for arrays of
63
	 * integers or strings.
64
	 * From https://stackoverflow.com/a/8827033
65
	 * @param array $b
66
	 * @param array $a
67
	 * @return array
68
	 */
69
	public static function arrayDiff(array $b, array $a) {
70
		$at = \array_flip($a);
71
		$d = [];
72
		foreach ($b as $i) {
73
			if (!isset($at[$i])) {
74
				$d[] = $i;
75
			}
76
		}
77
		return $d;
78
	}
79
80
	/**
81
	 * Get multiple items from @a $array, as indicated by a second array @a $indices.
82
	 * @param array $array
83
	 * @param array $indices
84
	 * @return array
85
	 */
86
	public static function arrayMultiGet(array $array, array $indices) {
87
		$result = [];
88
		foreach ($indices as $index) {
89
			$result[] = $array[$index];
90
		}
91
		return $result;
92
	}
93
94
	/**
95
	 * Convert the given array $arr so that keys of the potentially multi-dimensional array
96
	 * are converted using the mapping given in $dictionary. Keys not found from $dictionary
97
	 * are not altered. 
98
	 * @param array $arr
99
	 * @param array $dictionary
100
	 * @return array
101
	 */
102
	public static function convertArrayKeys(array $arr, array $dictionary) {
103
		$newArr = [];
104
105
		foreach ($arr as $k => $v) {
106
			$key = self::arrayGetOrDefault($dictionary, $k, $k);
107
			$newArr[$key] = is_array($v) ? self::convertArrayKeys($v, $dictionary) : $v;
108
		}
109
110
		return $newArr;
111
	}
112
113
	/**
114
	 * Get array value if exists, otherwise return a default value or null.
115
	 * The function supports getting value from a nested array by giving an array-type $key.
116
	 * In that case, the first value of the $key is used on the outer-most array, second value
117
	 * from $key on the next level, and so on. That is, arrayGetOrDefault($arr, ['a', 'b', 'c'])
118
	 * will return $arr['a']['b']['c'] if all the keys along the path are found.
119
	 *
120
	 * @param array $array
121
	 * @param int|string|array<int|string> $key
122
	 * @param mixed|null $default
123 3
	 * @return mixed|null
124 3
	 */
125 3
	public static function arrayGetOrDefault(array $array, $key, $default=null) {
126
		if (!\is_array($key)) {
127
			$key = [$key];
128 3
		} elseif (empty($key)) {
129 3
			throw new \InvalidArgumentException('Empty array is not a valid key');
130 3
		}
131
132
		$temp = $array;
133 3
		foreach ($key as $k) {
134
			if (isset($temp[$k])) {
135
				$temp = $temp[$k];
136
			} else {
137
				return $default;
138
			}
139
		}
140
		return $temp;
141
	}
142
143
	/**
144
	 * Like the built-in \explode(...) function but this one can be safely called with
145
	 * null string, and no warning will be emitted. Also, this returns an empty array from
146
	 * null and '' inputs while the built-in alternative returns a 1-item array containing
147
	 * an empty string.
148
	 * @param string $delimiter
149
	 * @param string|null $string
150
	 * @return array
151
	 */
152
	public static function explode($delimiter, $string) {
153
		if ($string === null || $string === '') {
154
			return [];
155
		} else {
156
			return \explode($delimiter, $string);
157
		}
158
	}
159
160
	/**
161
	 * Truncate the given string to maximum length, appendig ellipsis character
162
	 * if the truncation happened. Also null argument may be safely passed and
163
	 * it remains unaltered.
164 3
	 * @param string|null $string
165 3
	 * @param int $maxLength
166 1
	 * @return string|null
167
	 */
168 2
	public static function truncate($string, $maxLength) {
169
		if ($string === null) {
170
			return null;
171
		} else {
172
			return \mb_strimwidth($string, 0, $maxLength, "\u{2026}");
173
		}
174
	}
175
176
	/**
177
	 * Test if given string starts with another given string
178
	 * @param string $string
179
	 * @param string $potentialStart
180
	 * @param boolean $ignoreCase
181
	 * @return boolean
182
	 */
183
	public static function startsWith($string, $potentialStart, $ignoreCase=false) {
184
		$actualStart = \substr($string, 0, \strlen($potentialStart));
185
		if ($ignoreCase) {
186
			$actualStart= \mb_strtolower($actualStart);
187
			$potentialStart= \mb_strtolower($potentialStart);
188
		}
189
		return $actualStart === $potentialStart;
190
	}
191
192
	/**
193
	 * Test if given string ends with another given string
194
	 * @param string $string
195
	 * @param string $potentialEnd
196
	 * @param boolean $ignoreCase
197
	 * @return boolean
198
	 */
199
	public static function endsWith($string, $potentialEnd, $ignoreCase=false) {
200
		$actualEnd = \substr($string, -\strlen($potentialEnd));
201
		if ($ignoreCase) {
202
			$actualEnd = \mb_strtolower($actualEnd);
203
			$potentialEnd = \mb_strtolower($potentialEnd);
204
		}
205
		return $actualEnd === $potentialEnd;
206
	}
207
208
	/**
209
	 * Multi-byte safe case-insensitive string comparison
210
	 * @param string $a
211
	 * @param string $b
212
	 * @return int < 0 if $a is less than $b; > 0 if $a is greater than $b, and 0 if they are equal. 
213
	 */
214
	public static function stringCaseCompare($a, $b) {
215
		return \strcmp(\mb_strtolower($a), \mb_strtolower($b));
216
	}
217
218
	/**
219
	 * Convert file size given in bytes to human-readable format
220
	 * @param int $bytes
221
	 * @param int $decimals
222
	 * @return string
223
	 */
224
	public static function formatFileSize($bytes, $decimals = 1) {
225
		$units = 'BKMGTP';
226
		$factor = \floor((\strlen($bytes) - 1) / 3);
227
		return \sprintf("%.{$decimals}f", $bytes / \pow(1024, $factor)) . @$units[(int)$factor];
228
	}
229
230
	/**
231
	 * @param Folder $parentFolder
232
	 * @param string $relativePath
233
	 * @return Folder
234
	 */
235
	public static function getFolderFromRelativePath(Folder $parentFolder, $relativePath) {
236
		if ($relativePath !== null && $relativePath !== '/' && $relativePath !== '') {
237
			return $parentFolder->get($relativePath);
238
		} else {
239
			return $parentFolder;
240
		}
241
	}
242
243
	/**
244
	 * Create relative path from the given working dir (CWD) to the given target path
245
	 * @param string $cwdPath Absolute CWD path
246
	 * @param string $targetPath Absolute target path
247
	 * @return string
248
	 */
249
	public static function relativePath($cwdPath, $targetPath) {
250
		$cwdParts = \explode('/', $cwdPath);
251
		$targetParts = \explode('/', $targetPath);
252
253
		// remove the common prefix of the paths
254
		while (\count($cwdParts) > 0 && \count($targetParts) > 0 && $cwdParts[0] === $targetParts[0]) {
255
			\array_shift($cwdParts);
256
			\array_shift($targetParts);
257
		}
258
259
		// prepend up-navigation from CWD to the closest common parent folder with the target
260
		for ($i = 0, $count = \count($cwdParts); $i < $count; ++$i) {
261
			\array_unshift($targetParts, '..');
262
		}
263
264
		return \implode('/', $targetParts);
265
	}
266
267
	/**
268
	 * Given a current working directory path (CWD) and a relative path (possibly containing '..' parts),
269
	 * form an absolute path matching the relative path. This is a reverse operation for Util::relativePath().
270
	 * @param string $cwdPath
271
	 * @param string $relativePath
272
	 * @return string
273
	 */
274
	public static function resolveRelativePath($cwdPath, $relativePath) {
275
		$cwdParts = \explode('/', $cwdPath);
276
		$relativeParts = \explode('/', $relativePath);
277
278
		// get rid of the trailing empty part of CWD which appears when CWD has a trailing '/'
279
		if ($cwdParts[\count($cwdParts)-1] === '') {
280
			\array_pop($cwdParts);
281
		}
282
283
		foreach ($relativeParts as $part) {
284
			if ($part === '..') {
285
				\array_pop($cwdParts);
286
			} else {
287
				\array_push($cwdParts, $part);
288
			}
289
		}
290
291
		return \implode('/', $cwdParts);
292
	}
293
294
	/**
295
	 * Encode a file path so that it can be used as part of a WebDAV URL
296
	 * @param string $path
297
	 * @return string
298
	 */
299
	public static function urlEncodePath($path) {
300
		// URL encode each part of the file path
301
		return \join('/', \array_map('rawurlencode', \explode('/', $path)));
302
	}
303
304
	/**
305
	 * Swap values of two variables in place
306
	 * @param mixed $a
307
	 * @param mixed $b
308
	 */
309
	public static function swap(&$a, &$b) {
310
		$temp = $a;
311
		$a = $b;
312
		$b = $temp;
313
	}
314
}
315