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

TemplateManager   F

Complexity

Total Complexity 60

Size/Duplication

Total Lines 300
Duplicated Lines 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 169
c 5
b 0
f 0
dl 0
loc 300
rs 3.6
wmc 60

15 Methods

Rating   Name   Duplication   Size   Complexity  
A getRegisteredProviders() 0 12 3
A __construct() 0 24 2
A registerTemplateFileCreator() 0 2 1
A getTypes() 0 8 3
B createFromTemplate() 0 27 6
A getTemplateFolder() 0 5 2
A listCreators() 0 6 1
A listTemplates() 0 6 1
A setTemplatePath() 0 2 1
A getLocalizedTemplatePath() 0 14 5
A formatFile() 0 11 1
F initializeTemplateDirectory() 0 79 24
A hasTemplateDirectory() 0 7 2
B getTemplateFiles() 0 27 7
A getTemplatePath() 0 2 1

How to fix   Complexity   

Complex Class

Complex classes like TemplateManager often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use TemplateManager, and based on these observations, apply Extract Interface, too.

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