Passed
Push — master ( 7e7284...62fa85 )
by Roeland
17:42 queued 12s
created

TemplateManager   D

Complexity

Total Complexity 59

Size/Duplication

Total Lines 294
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 165
dl 0
loc 294
c 2
b 0
f 0
rs 4.08
wmc 59

15 Methods

Rating   Name   Duplication   Size   Complexity  
A getRegisteredProviders() 0 12 3
A getTypes() 0 5 2
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 __construct() 0 22 2
A getTemplatePath() 0 2 1
A registerTemplateFileCreator() 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 OCP\EventDispatcher\IEventDispatcher;
32
use OCP\Files\Folder;
33
use OCP\Files\File;
34
use OCP\Files\GenericFileException;
35
use OCP\Files\IRootFolder;
36
use OCP\Files\Node;
37
use OCP\Files\NotFoundException;
38
use OCP\Files\NotPermittedException;
39
use OCP\Files\Template\FileCreatedFromTemplateEvent;
40
use OCP\Files\Template\ICustomTemplateProvider;
41
use OCP\Files\Template\ITemplateManager;
42
use OCP\Files\Template\Template;
43
use OCP\Files\Template\TemplateFileCreator;
44
use OCP\IConfig;
45
use OCP\IPreview;
46
use OCP\IServerContainer;
47
use OCP\IUserSession;
48
use OCP\L10N\IFactory;
49
use Psr\Log\LoggerInterface;
50
51
class TemplateManager implements ITemplateManager {
52
	private $registeredTypes = [];
53
	private $types = [];
54
55
	/** @var array|null */
56
	private $providers = null;
57
58
	private $serverContainer;
59
	private $eventDispatcher;
60
	private $rootFolder;
61
	private $previewManager;
62
	private $config;
63
	private $l10n;
64
	private $logger;
65
	private $userId;
66
	private $l10nFactory;
67
	/** @var Coordinator */
68
	private $bootstrapCoordinator;
69
70
	public function __construct(
71
		IServerContainer $serverContainer,
72
		IEventDispatcher $eventDispatcher,
73
		Coordinator $coordinator,
74
		IRootFolder $rootFolder,
75
		IUserSession $userSession,
76
		IPreview $previewManager,
77
		IConfig $config,
78
		IFactory $l10nFactory,
79
		LoggerInterface $logger
80
	) {
81
		$this->serverContainer = $serverContainer;
82
		$this->eventDispatcher = $eventDispatcher;
83
		$this->bootstrapCoordinator = $coordinator;
84
		$this->rootFolder = $rootFolder;
85
		$this->previewManager = $previewManager;
86
		$this->config = $config;
87
		$this->l10nFactory = $l10nFactory;
88
		$this->l10n = $l10nFactory->get('lib');
89
		$this->logger = $logger;
90
		$user = $userSession->getUser();
91
		$this->userId = $user ? $user->getUID() : null;
92
	}
93
94
	public function registerTemplateFileCreator(callable $callback): void {
95
		$this->registeredTypes[] = $callback;
96
	}
97
98
	public function getRegisteredProviders(): array {
99
		if ($this->providers !== null) {
100
			return $this->providers;
101
		}
102
103
		$context = $this->bootstrapCoordinator->getRegistrationContext();
104
105
		$this->providers = [];
106
		foreach ($context->getTemplateProviders() as $provider) {
107
			$this->providers[$provider['class']] = $this->serverContainer->get($provider['class']);
108
		}
109
		return $this->providers;
110
	}
111
112
	public function getTypes(): array {
113
		foreach ($this->registeredTypes as $registeredType) {
114
			$this->types[] = $registeredType();
115
		}
116
		return $this->types;
117
	}
118
119
	public function listCreators(): array {
120
		$types = $this->getTypes();
121
		usort($types, function (TemplateFileCreator $a, TemplateFileCreator $b) {
122
			return $a->getOrder() - $b->getOrder();
123
		});
124
		return $types;
125
	}
126
127
	public function listTemplates(): array {
128
		return array_map(function (TemplateFileCreator $entry) {
129
			return array_merge($entry->jsonSerialize(), [
130
				'templates' => $this->getTemplateFiles($entry)
131
			]);
132
		}, $this->listCreators());
133
	}
134
135
	/**
136
	 * @param string $filePath
137
	 * @param string $templateId
138
	 * @return array
139
	 * @throws GenericFileException
140
	 */
141
	public function createFromTemplate(string $filePath, string $templateId = '', string $templateType = 'user'): array {
142
		$userFolder = $this->rootFolder->getUserFolder($this->userId);
143
		try {
144
			$userFolder->get($filePath);
145
			throw new GenericFileException($this->l10n->t('File already exists'));
146
		} catch (NotFoundException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
147
		}
148
		try {
149
			$targetFile = $userFolder->newFile($filePath);
150
			if ($templateType === 'user' && $templateId !== '') {
151
				$template = $userFolder->get($templateId);
152
				$template->copy($targetFile->getPath());
153
			} else {
154
				$matchingProvider = array_filter($this->getRegisteredProviders(), function (ICustomTemplateProvider $provider) use ($templateType) {
155
					return $templateType === get_class($provider);
156
				});
157
				$provider = array_shift($matchingProvider);
158
				if ($provider) {
159
					$template = $provider->getCustomTemplate($templateId);
160
					$template->copy($targetFile->getPath());
161
				}
162
			}
163
			$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...
164
			return $this->formatFile($userFolder->get($filePath));
165
		} catch (\Exception $e) {
166
			$this->logger->error($e->getMessage(), ['exception' => $e]);
167
			throw new GenericFileException($this->l10n->t('Failed to create file from template'));
168
		}
169
	}
170
171
	/**
172
	 * @return Folder
173
	 * @throws \OCP\Files\NotFoundException
174
	 * @throws \OCP\Files\NotPermittedException
175
	 * @throws \OC\User\NoUserException
176
	 */
177
	private function getTemplateFolder(): Node {
178
		if ($this->getTemplatePath() !== '') {
179
			return $this->rootFolder->getUserFolder($this->userId)->get($this->getTemplatePath());
180
		}
181
		throw new NotFoundException();
182
	}
183
184
	private function getTemplateFiles(TemplateFileCreator $type): array {
185
		$templates = [];
186
		foreach ($this->getRegisteredProviders() as $provider) {
187
			foreach ($type->getMimetypes() as $mimetype) {
188
				foreach ($provider->getCustomTemplates($mimetype) as $template) {
189
					$templates[] = $template;
190
				}
191
			}
192
		}
193
		try {
194
			$userTemplateFolder = $this->getTemplateFolder();
195
		} catch (\Exception $e) {
196
			return $templates;
197
		}
198
		foreach ($type->getMimetypes() as $mimetype) {
199
			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

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

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