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

TemplateManager::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 22
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 11
nc 2
nop 9
dl 0
loc 22
c 1
b 0
f 0
cc 2
rs 9.9

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 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