Passed
Push — master ( 7b8fea...bb5769 )
by Pauli
10:49
created

Util::getFolderFromRelativePath()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 10
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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