Passed
Push — master ( ad2033...9db3b2 )
by Roeland
12:21 queued 15s
created

TemplateManager::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 24
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 2
eloc 12
nc 2
nop 10
dl 0
loc 24
rs 9.8666
c 2
b 0
f 0

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
2
/**
3
 * @copyright Copyright (c) 2021 Julius Härtl <[email protected]>
4
 *
5
 * @author Julius Härtl <[email protected]>
6
 *
7
 * @license GNU AGPL version 3 or any later version
8
 *
9
 * This program is free software: you can redistribute it and/or modify
10
 * it under the terms of the GNU Affero General Public License as
11
 * published by the Free Software Foundation, either version 3 of the
12
 * License, or (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
 * GNU Affero General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU Affero General Public License
20
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21
 *
22
 */
23
24
declare(strict_types=1);
25
26
27
namespace OC\Files\Template;
28
29
use OC\AppFramework\Bootstrap\Coordinator;
30
use OC\Files\Cache\Scanner;
31
use OC\Files\Filesystem;
32
use OCP\EventDispatcher\IEventDispatcher;
33
use OCP\Files\Folder;
34
use OCP\Files\File;
35
use OCP\Files\GenericFileException;
36
use OCP\Files\IRootFolder;
37
use OCP\Files\Node;
38
use OCP\Files\NotFoundException;
39
use OCP\Files\NotPermittedException;
40
use OCP\Files\Template\FileCreatedFromTemplateEvent;
41
use OCP\Files\Template\ICustomTemplateProvider;
42
use OCP\Files\Template\ITemplateManager;
43
use OCP\Files\Template\Template;
44
use OCP\Files\Template\TemplateFileCreator;
45
use OCP\IConfig;
46
use OCP\IPreview;
47
use OCP\IServerContainer;
48
use OCP\IUserManager;
49
use OCP\IUserSession;
50
use OCP\L10N\IFactory;
51
use Psr\Log\LoggerInterface;
52
53
class TemplateManager implements ITemplateManager {
54
	private $registeredTypes = [];
55
	private $types = [];
56
57
	/** @var array|null */
58
	private $providers = null;
59
60
	private $serverContainer;
61
	private $eventDispatcher;
62
	private $rootFolder;
63
	private $userManager;
64
	private $previewManager;
65
	private $config;
66
	private $l10n;
67
	private $logger;
68
	private $userId;
69
	private $l10nFactory;
70
	/** @var Coordinator */
71
	private $bootstrapCoordinator;
72
73
	public function __construct(
74
		IServerContainer $serverContainer,
75
		IEventDispatcher $eventDispatcher,
76
		Coordinator $coordinator,
77
		IRootFolder $rootFolder,
78
		IUserSession $userSession,
79
		IUserManager $userManager,
80
		IPreview $previewManager,
81
		IConfig $config,
82
		IFactory $l10nFactory,
83
		LoggerInterface $logger
84
	) {
85
		$this->serverContainer = $serverContainer;
86
		$this->eventDispatcher = $eventDispatcher;
87
		$this->bootstrapCoordinator = $coordinator;
88
		$this->rootFolder = $rootFolder;
89
		$this->userManager = $userManager;
90
		$this->previewManager = $previewManager;
91
		$this->config = $config;
92
		$this->l10nFactory = $l10nFactory;
93
		$this->l10n = $l10nFactory->get('lib');
94
		$this->logger = $logger;
95
		$user = $userSession->getUser();
96
		$this->userId = $user ? $user->getUID() : null;
97
	}
98
99
	public function registerTemplateFileCreator(callable $callback): void {
100
		$this->registeredTypes[] = $callback;
101
	}
102
103
	public function getRegisteredProviders(): array {
104
		if ($this->providers !== null) {
105
			return $this->providers;
106
		}
107
108
		$context = $this->bootstrapCoordinator->getRegistrationContext();
109
110
		$this->providers = [];
111
		foreach ($context->getTemplateProviders() as $provider) {
112
			$this->providers[$provider['class']] = $this->serverContainer->get($provider['class']);
113
		}
114
		return $this->providers;
115
	}
116
117
	public function getTypes(): array {
118
		if (!empty($this->types)) {
119
			return $this->types;
120
		}
121
		foreach ($this->registeredTypes as $registeredType) {
122
			$this->types[] = $registeredType();
123
		}
124
		return $this->types;
125
	}
126
127
	public function listCreators(): array {
128
		$types = $this->getTypes();
129
		usort($types, function (TemplateFileCreator $a, TemplateFileCreator $b) {
130
			return $a->getOrder() - $b->getOrder();
131
		});
132
		return $types;
133
	}
134
135
	public function listTemplates(): array {
136
		return array_map(function (TemplateFileCreator $entry) {
137
			return array_merge($entry->jsonSerialize(), [
138
				'templates' => $this->getTemplateFiles($entry)
139
			]);
140
		}, $this->listCreators());
141
	}
142
143
	/**
144
	 * @param string $filePath
145
	 * @param string $templateId
146
	 * @return array
147
	 * @throws GenericFileException
148
	 */
149
	public function createFromTemplate(string $filePath, string $templateId = '', string $templateType = 'user'): array {
150
		$userFolder = $this->rootFolder->getUserFolder($this->userId);
151
		try {
152
			$userFolder->get($filePath);
153
			throw new GenericFileException($this->l10n->t('File already exists'));
154
		} catch (NotFoundException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
155
		}
156
		try {
157
			$targetFile = $userFolder->newFile($filePath);
158
			if ($templateType === 'user' && $templateId !== '') {
159
				$template = $userFolder->get($templateId);
160
				$template->copy($targetFile->getPath());
161
			} else {
162
				$matchingProvider = array_filter($this->getRegisteredProviders(), function (ICustomTemplateProvider $provider) use ($templateType) {
163
					return $templateType === get_class($provider);
164
				});
165
				$provider = array_shift($matchingProvider);
166
				if ($provider) {
167
					$template = $provider->getCustomTemplate($templateId);
168
					$template->copy($targetFile->getPath());
169
				}
170
			}
171
			$this->eventDispatcher->dispatchTyped(new FileCreatedFromTemplateEvent($template, $targetFile));
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $template does not seem to be defined for all execution paths leading up to this point.
Loading history...
172
			return $this->formatFile($userFolder->get($filePath));
173
		} catch (\Exception $e) {
174
			$this->logger->error($e->getMessage(), ['exception' => $e]);
175
			throw new GenericFileException($this->l10n->t('Failed to create file from template'));
176
		}
177
	}
178
179
	/**
180
	 * @return Folder
181
	 * @throws \OCP\Files\NotFoundException
182
	 * @throws \OCP\Files\NotPermittedException
183
	 * @throws \OC\User\NoUserException
184
	 */
185
	private function getTemplateFolder(): Node {
186
		if ($this->getTemplatePath() !== '') {
187
			return $this->rootFolder->getUserFolder($this->userId)->get($this->getTemplatePath());
188
		}
189
		throw new NotFoundException();
190
	}
191
192
	private function getTemplateFiles(TemplateFileCreator $type): array {
193
		$templates = [];
194
		foreach ($this->getRegisteredProviders() as $provider) {
195
			foreach ($type->getMimetypes() as $mimetype) {
196
				foreach ($provider->getCustomTemplates($mimetype) as $template) {
197
					$templates[] = $template;
198
				}
199
			}
200
		}
201
		try {
202
			$userTemplateFolder = $this->getTemplateFolder();
203
		} catch (\Exception $e) {
204
			return $templates;
205
		}
206
		foreach ($type->getMimetypes() as $mimetype) {
207
			foreach ($userTemplateFolder->searchByMime($mimetype) as $templateFile) {
0 ignored issues
show
Bug introduced by
The method searchByMime() does not exist on OCP\Files\Node. It seems like you code against a sub-type of OCP\Files\Node such as OCP\Files\Folder or OC\Files\Node\Folder. ( Ignorable by Annotation )

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

207
			foreach ($userTemplateFolder->/** @scrutinizer ignore-call */ searchByMime($mimetype) as $templateFile) {
Loading history...
208
				$template = new Template(
209
					'user',
210
					$this->rootFolder->getUserFolder($this->userId)->getRelativePath($templateFile->getPath()),
211
					$templateFile
212
				);
213
				$template->setHasPreview($this->previewManager->isAvailable($templateFile));
214
				$templates[] = $template;
215
			}
216
		}
217
218
		return $templates;
219
	}
220
221
	/**
222
	 * @param Node|File $file
223
	 * @return array
224
	 * @throws NotFoundException
225
	 * @throws \OCP\Files\InvalidPathException
226
	 */
227
	private function formatFile(Node $file): array {
228
		return [
229
			'basename' => $file->getName(),
230
			'etag' => $file->getEtag(),
231
			'fileid' => $file->getId(),
232
			'filename' => $this->rootFolder->getUserFolder($this->userId)->getRelativePath($file->getPath()),
233
			'lastmod' => $file->getMTime(),
234
			'mime' => $file->getMimetype(),
235
			'size' => $file->getSize(),
236
			'type' => $file->getType(),
237
			'hasPreview' => $this->previewManager->isAvailable($file)
238
		];
239
	}
240
241
	public function hasTemplateDirectory(): bool {
242
		try {
243
			$this->getTemplateFolder();
244
			return true;
245
		} catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
246
		}
247
		return false;
248
	}
249
250
	public function setTemplatePath(string $path): void {
251
		$this->config->setUserValue($this->userId, 'core', 'templateDirectory', $path);
252
	}
253
254
	public function getTemplatePath(): string {
255
		return $this->config->getUserValue($this->userId, 'core', 'templateDirectory', '');
256
	}
257
258
	public function initializeTemplateDirectory(string $path = null, string $userId = null, $copyTemplates = true): string {
259
		if ($userId !== null) {
260
			$this->userId = $userId;
261
		}
262
263
		$defaultSkeletonDirectory = \OC::$SERVERROOT . '/core/skeleton';
264
		$defaultTemplateDirectory = \OC::$SERVERROOT . '/core/skeleton/Templates';
265
		$skeletonPath = $this->config->getSystemValue('skeletondirectory', $defaultSkeletonDirectory);
266
		$skeletonTemplatePath = $this->config->getSystemValue('templatedirectory', $defaultTemplateDirectory);
267
		$isDefaultSkeleton = $skeletonPath === $defaultSkeletonDirectory;
268
		$isDefaultTemplates = $skeletonTemplatePath === $defaultTemplateDirectory;
269
		$userLang = $this->l10nFactory->getUserLanguage($this->userManager->get($this->userId));
270
271
		try {
272
			$l10n = $this->l10nFactory->get('lib', $userLang);
273
			$userFolder = $this->rootFolder->getUserFolder($this->userId);
274
			$userTemplatePath = $path ?? $l10n->t('Templates') . '/';
275
276
			// Initial user setup without a provided path
277
			if ($path === null) {
278
				// All locations are default so we just need to rename the directory to the users language
279
				if ($isDefaultSkeleton && $isDefaultTemplates) {
280
					if (!$userFolder->nodeExists('Templates')) {
281
						return '';
282
					}
283
					$newPath = Filesystem::normalizePath($userFolder->getPath() . '/' . $userTemplatePath);
284
					if ($newPath !== $userFolder->get('Templates')->getPath()) {
285
						$userFolder->get('Templates')->move($newPath);
286
					}
287
					$this->setTemplatePath($userTemplatePath);
288
					return $userTemplatePath;
289
				}
290
291
				if ($isDefaultSkeleton && !empty($skeletonTemplatePath) && !$isDefaultTemplates && $userFolder->nodeExists('Templates')) {
292
					$shippedSkeletonTemplates = $userFolder->get('Templates');
293
					$shippedSkeletonTemplates->delete();
294
				}
295
			}
296
297
			try {
298
				$folder = $userFolder->newFolder($userTemplatePath);
299
			} catch (NotPermittedException $e) {
300
				$folder = $userFolder->get($userTemplatePath);
301
			}
302
303
			$folderIsEmpty = count($folder->getDirectoryListing()) === 0;
0 ignored issues
show
Bug introduced by
The method getDirectoryListing() does not exist on OCP\Files\Node. It seems like you code against a sub-type of OCP\Files\Node such as OCP\Files\Folder or OC\Files\Node\Folder. ( Ignorable by Annotation )

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

303
			$folderIsEmpty = count($folder->/** @scrutinizer ignore-call */ getDirectoryListing()) === 0;
Loading history...
304
305
			if (!$copyTemplates) {
306
				$this->setTemplatePath($userTemplatePath);
307
				return $userTemplatePath;
308
			}
309
310
			if (!$isDefaultTemplates && $folderIsEmpty) {
311
				$localizedSkeletonTemplatePath = $this->getLocalizedTemplatePath($skeletonTemplatePath, $userLang);
312
				if (!empty($localizedSkeletonTemplatePath) && file_exists($localizedSkeletonTemplatePath)) {
313
					\OC_Util::copyr($localizedSkeletonTemplatePath, $folder);
314
					$userFolder->getStorage()->getScanner()->scan($folder->getInternalPath(), Scanner::SCAN_RECURSIVE);
315
					$this->setTemplatePath($userTemplatePath);
316
					return $userTemplatePath;
317
				}
318
			}
319
320
			if ($path !== null && $isDefaultSkeleton && $isDefaultTemplates && $folderIsEmpty) {
321
				$localizedSkeletonPath = $this->getLocalizedTemplatePath($skeletonPath . '/Templates', $userLang);
322
				if (!empty($localizedSkeletonPath) && file_exists($localizedSkeletonPath)) {
323
					\OC_Util::copyr($localizedSkeletonPath, $folder);
324
					$userFolder->getStorage()->getScanner()->scan($folder->getInternalPath(), Scanner::SCAN_RECURSIVE);
325
					$this->setTemplatePath($userTemplatePath);
326
					return $userTemplatePath;
327
				}
328
			}
329
330
			$this->setTemplatePath($path ?? '');
331
			return $this->getTemplatePath();
332
		} catch (\Throwable $e) {
333
			$this->logger->error('Failed to initialize templates directory to user language ' . $userLang . ' for ' . $userId, ['app' => 'files_templates', 'exception' => $e]);
334
		}
335
		$this->setTemplatePath('');
336
		return $this->getTemplatePath();
337
	}
338
339
	private function getLocalizedTemplatePath(string $skeletonTemplatePath, string $userLang) {
340
		$localizedSkeletonTemplatePath = str_replace('{lang}', $userLang, $skeletonTemplatePath);
341
342
		if (!file_exists($localizedSkeletonTemplatePath)) {
343
			$dialectStart = strpos($userLang, '_');
344
			if ($dialectStart !== false) {
345
				$localizedSkeletonTemplatePath = str_replace('{lang}', substr($userLang, 0, $dialectStart), $skeletonTemplatePath);
346
			}
347
			if ($dialectStart === false || !file_exists($localizedSkeletonTemplatePath)) {
348
				$localizedSkeletonTemplatePath = str_replace('{lang}', 'default', $skeletonTemplatePath);
349
			}
350
		}
351
352
		return $localizedSkeletonTemplatePath;
353
	}
354
}
355