TemplateManager   C
last analyzed

Complexity

Total Complexity 55

Size/Duplication

Total Lines 461
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 55
lcom 1
cbo 8
dl 0
loc 461
ccs 0
cts 236
cp 0
rs 6
c 0
b 0
f 0

23 Methods

Rating   Name   Duplication   Size   Complexity  
A getEmpty() 0 22 3
A updateEmptyTemplates() 0 9 2
A getSystem() 0 6 1
A getUser() 0 10 2
A getAllFormatted() 0 10 2
A add() 0 12 2
A delete() 0 11 3
A flipTypes() 0 8 2
A getUserTemplateDir() 0 26 5
A __construct() 0 19 1
A createAppDataFolders() 0 17 3
A setUserId() 0 3 1
B get() 0 28 6
A filterTemplates() 0 9 2
A getSystemFormatted() 0 14 1
A getUserFormatted() 0 7 1
A getAll() 0 17 4
A getSystemTemplateDir() 0 4 1
A getEmptyTemplateDir() 0 4 1
A formatNodeReturn() 0 12 2
A isTemplate() 0 15 3
A formatEmpty() 0 10 2
A isValidTemplateMime() 0 14 5

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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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
declare (strict_types = 1);
3
/**
4
 * @copyright Copyright (c) 2018 John Molakvoæ <[email protected]>
5
 *
6
 * @author John Molakvoæ <[email protected]>
7
 *
8
 * @license GNU AGPL version 3 or any later version
9
 *
10
 * This program is free software: you can redistribute it and/or modify
11
 * it under the terms of the GNU Affero General Public License as
12
 * published by the Free Software Foundation, either version 3 of the
13
 * License, or (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 * GNU Affero General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU Affero General Public License
21
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22
 *
23
 */
24
25
namespace OCA\Richdocuments;
26
27
use OCP\Files\File;
28
use OCP\Files\Folder;
29
use OCP\Files\IAppData;
30
use OCP\Files\IRootFolder;
31
use OCP\Files\Node;
32
use OCP\Files\NotFoundException;
33
use OCP\IConfig;
34
use OCP\IL10N;
35
use OCP\IPreview;
36
use OCP\IURLGenerator;
37
use OC\Files\AppData\Factory;
38
39
class TemplateManager {
40
41
	/** @var string */
42
	protected $appName;
43
44
	/** @var string */
45
	protected $userId;
46
47
	/** @var IConfig */
48
	private $config;
49
50
	/** @var IURLGenerator */
51
	private $urlGenerator;
52
53
	/** @var IRootFolder */
54
	private $rootFolder;
55
56
	/** @var IL10N */
57
	private $l;
58
59
	/** Accepted templates mime types */
60
	const MIMES_DOCUMENTS = [
61
		'application/vnd.oasis.opendocument.text-template',
62
		'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
63
		'application/msword'
64
	];
65
	const MIMES_SHEETS = [
66
		'application/vnd.oasis.opendocument.spreadsheet-template',
67
		'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
68
		'application/vnd.ms-excel'
69
	];
70
	const MIMES_PRESENTATIONS = [
71
		'application/vnd.oasis.opendocument.presentation-template',
72
		'application/vnd.openxmlformats-officedocument.presentationml.template',
73
		'application/vnd.ms-powerpoint'
74
	];
75
76
	/** @var array Template mime types match */
77
	static public $tplTypes = [
78
		'document'     => self::MIMES_DOCUMENTS,
79
		'spreadsheet'  => self::MIMES_SHEETS,
80
		'presentation' => self::MIMES_PRESENTATIONS
81
	];
82
83
	const TYPE_EXTENTION = [
84
		'document'     => 'odt',
85
		'spreadsheet'  => 'ods',
86
		'presentation' => 'odp'
87
	];
88
89
	const TYPE_EXTENSION_OOXML = [
90
		'document'     => 'docx',
91
		'spreadsheet'  => 'xlsx',
92
		'presentation' => 'pptx'
93
	];
94
95
	const EMPTY_TEMPLATE_ID_TYPE = [
96
		-1 => 'document',
97
		-2 => 'spreadsheet',
98
		-3 => 'presentation',
99
	];
100
	const EMPTY_TEMPLATE_TYPE_ID = [
101
		'document'     => -1,
102
		'spreadsheet'  => -2,
103
		'presentation' => -3,
104
	];
105
106
107
	/**
108
	 * Template manager
109
	 *
110
	 * @param string $appName
111
	 * @param string $userId
112
	 * @param IConfig $config
113
	 * @param Factory $appDataFactory
0 ignored issues
show
Documentation introduced by
There is no parameter named $appDataFactory. Did you maybe mean $appData?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
114
	 * @param IURLGenerator $urlGenerator
115
	 * @param IRootFolder $rootFolder
116
	 * @param IL10N $l
117
	 * @throws \OCP\Files\NotPermittedException
118
	 */
119
	public function __construct($appName,
120
								$userId,
121
								IConfig $config,
122
								IAppData $appData,
123
								IURLGenerator $urlGenerator,
124
								IRootFolder $rootFolder,
125
								IL10N $l) {
126
		$this->appName        = $appName;
127
		$this->userId         = $userId;
128
		$this->config         = $config;
129
		$this->rootFolder     = $rootFolder;
130
		$this->urlGenerator   = $urlGenerator;
131
132
133
		$this->appData = $appData;
0 ignored issues
show
Bug introduced by
The property appData does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
134
		$this->createAppDataFolders();
135
136
		$this->l = $l;
137
	}
138
139
	private function createAppDataFolders() {
140
		/*
141
		 * Init the appdata folder
142
		 * We need an actual folder for the fileid and previews.
143
		 * TODO: Fix this at some point
144
		 */
145
		try {
146
			$this->appData->getFolder('templates');
147
		} catch (NotFoundException $e) {
148
			$this->appData->newFolder('templates');
149
		}
150
		try {
151
			$this->appData->getFolder('empty_templates');
152
		} catch (NotFoundException $e) {
153
			$this->appData->newFolder('empty_templates');
154
		}
155
	}
156
157
	public function setUserId($userId) {
158
		$this->userId = $userId;
159
	}
160
161
	/**
162
	 * Get template ISimpleFile|Node
163
	 *
164
	 * @param int $fileId
165
	 * @return File
166
	 */
167
	public function get($fileId) {
168
		// is this a global template ?
169
		$files = $this->getEmptyTemplateDir()->getDirectoryListing();
170
171
		foreach ($files as $file) {
172
			if ($file->getId() === $fileId) {
173
				return $file;
174
			}
175
		}
176
177
		// is this a global template ?
178
		$files = $this->getSystemTemplateDir()->getDirectoryListing();
179
180
		foreach ($files as $file) {
181
			if ($file->getId() === $fileId) {
182
				return $file;
183
			}
184
		}
185
186
		$templateDir = $this->getUserTemplateDir();
187
		// finally get the template file
188
		$files = $templateDir->getById($fileId);
189
		if ($files !== []) {
190
			return $files[0];
191
		}
192
193
		throw new NotFoundException();
194
	}
195
196
	/**
197
	 * @param File[] $templates
198
	 * @return File[]
199
	 */
200
	private function filterTemplates($templates, $type = null) {
201
		return array_filter($templates, function (Node $templateFile) use ($type) {
202
			if (!($templateFile instanceof File)) {
203
				return false;
204
			}
205
206
			return $this->isValidTemplateMime($templateFile->getMimeType(), $type);
207
		});
208
	}
209
210
	private function getEmpty($type = null) {
211
		$folder = $this->getEmptyTemplateDir();
212
213
		$templateFiles = $folder->getDirectoryListing();
214
215
		if ($templateFiles === []) {
216
			// Empty so lets copy over the basic templates
217
			$templates = [
218
				'document.ott',
219
				'spreadsheet.ots',
220
				'presentation.otp',
221
			];
222
223
			foreach ($templates as $template) {
224
				$file = $folder->newFile($template);
225
				$file->putContent(file_get_contents(__DIR__ . '/../assets/' . $template));
226
				$templateFiles[] = $file;
227
			}
228
		}
229
230
		return $this->filterTemplates($templateFiles, $type);
0 ignored issues
show
Documentation introduced by
$templateFiles is of type array<integer,object<OCP\Files\Node>>, but the function expects a array<integer,object<OCP\Files\File>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
231
	}
232
233
	/**
234
	 * Remove empty_templates in appdata and recreate it from the apps templates
235
	 */
236
	public function updateEmptyTemplates() {
237
		try {
238
			$folder = $this->getEmptyTemplateDir();
239
			$folder->delete();
240
		} catch (NotFoundException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
241
		}
242
		$this->appData->newFolder('empty_templates');
243
		$this->getEmpty();
244
	}
245
246
	/**
247
	 * Get all global templates
248
	 *
249
	 * @return File[]
250
	 */
251
	public function getSystem($type = null) {
252
		$folder = $this->getSystemTemplateDir();
253
254
		$templateFiles = $folder->getDirectoryListing();
255
		return $this->filterTemplates($templateFiles, $type);
0 ignored issues
show
Documentation introduced by
$templateFiles is of type array<integer,object<OCP\Files\Node>>, but the function expects a array<integer,object<OCP\Files\File>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
256
	}
257
258
	/**
259
	 * @return array
260
	 */
261
	public function getSystemFormatted($type = null) {
262
		$empty = $this->getEmpty($type);
263
		$system = $this->getSystem($type);
264
265
		$emptyFormatted = array_map(function(File $file) {
266
			return $this->formatEmpty($file);
267
		}, $empty);
268
269
		$systemFormatted = array_map(function(File $file) {
270
			return $this->formatNodeReturn($file);
271
		}, $system);
272
273
		return array_merge($emptyFormatted, $systemFormatted);
274
	}
275
276
	/**
277
	 * Get all user templates
278
	 *
279
	 * @return File[]
280
	 */
281
	public function getUser($type = null) {
282
		try {
283
			$templateDir   = $this->getUserTemplateDir();
284
			$templateFiles = $templateDir->getDirectoryListing();
285
286
			return $this->filterTemplates($templateFiles, $type);
0 ignored issues
show
Documentation introduced by
$templateFiles is of type array<integer,object<OCP\Files\Node>>, but the function expects a array<integer,object<OCP\Files\File>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
287
		} catch(NotFoundException $e) {
288
			return [];
289
		}
290
	}
291
292
	/**
293
	 * @return array
294
	 */
295
	public function getUserFormatted($type) {
296
		$templates = $this->getUser($type);
297
298
		return array_map(function(File $file) {
299
			return $this->formatNodeReturn($file);
300
		}, $templates);
301
	}
302
303
	/**
304
	 * Get all templates
305
	 *
306
	 * @return File[]
307
	 */
308
	public function getAll($type = 'document') {
309
		$system = $this->getSystem();
310
		$user   = $this->getUser();
311
312
		if (!array_key_exists($type, self::$tplTypes)) {
313
			return [];
314
		}
315
316
		return array_values(array_filter(array_merge($user, $system), function (File $template) use ($type) {
317
			foreach (self::$tplTypes[$type] as $mime) {
318
				if ($template->getMimeType() === $mime) {
319
					return true;
320
				}
321
			}
322
			return false;
323
		}));
324
	}
325
326
	public function getAllFormatted($type) {
327
		if (!array_key_exists($type, self::$tplTypes)) {
328
			return [];
329
		}
330
331
		$system = $this->getSystemFormatted($type);
332
		$user   = $this->getUserFormatted($type);
333
334
		return array_merge($system, $user);
335
	}
336
337
	/**
338
	 * Add a template to the global template folder
339
	 *
340
	 * @param string $templateName
341
	 * @param string $templateFile
342
	 * @return array
343
	 */
344
	public function add($templateName, $templateFile) {
345
		$folder = $this->getSystemTemplateDir();
346
347
		try {
348
			$template = $folder->get($templateName);
349
		} catch (NotFoundException $e) {
350
			$template = $folder->newFile($templateName);
351
		}
352
		$template->putContent($templateFile);
0 ignored issues
show
Bug introduced by
The method putContent() does not seem to exist on object<OCP\Files\Node>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
353
354
		return $this->formatNodeReturn($this->get($template->getId()));
355
	}
356
357
	/**
358
	 * Delete a template to the global template folder
359
	 *
360
	 * @param int $fileId
361
	 * @return boolean
362
	 * @throws NotFoundException
363
	 */
364
	public function delete($fileId) {
365
		$files = $this->getSystemTemplateDir()->getDirectoryListing();
366
		foreach ($files as $file) {
367
			if ($file->getId() === $fileId) {
368
				$file->delete();
369
				return true;
370
			}
371
		}
372
373
		throw new NotFoundException();
374
	}
375
376
	/**
377
	 * Flip $tplTypes to retrieve types by mime
378
	 *
379
	 * @return array
380
	 */
381
	private function flipTypes() {
382
		$result = [];
383
		foreach ($this::$tplTypes as $type => $mime) {
384
			$result = array_merge($result, array_fill_keys($mime, $type));
385
		}
386
387
		return $result;
388
	}
389
390
	/**
391
	 * Get the user template directory
392
	 *
393
	 * @return Folder
394
	 * @throws NotFoundException
395
	 */
396
	private function getUserTemplateDir() {
397
		if ($this->userId === null) {
398
			throw new NotFoundException('userId not set');
399
		}
400
401
		// has the user manually set a directory as the default template dir ?
402
		$templateDirPath = $this->config->getUserValue($this->userId, $this->appName, 'templateFolder', false);
403
		$userFolder = $this->rootFolder->getUserFolder($this->userId);
404
405
		if ($templateDirPath !== false) {
406
			$templateDir = $userFolder->get($templateDirPath);
407
		} else {
408
			// fallback to default template dir
409
			try {
410
				$templateDir = $userFolder->get('Templates');
411
			} catch (NotFoundException $e) {
412
				throw new NotFoundException($e->getMessage());
413
			}
414
		}
415
416
		if (!($templateDir instanceof Folder)) {
417
			throw new NotFoundException('Template dir points to a file');
418
		}
419
420
		return $templateDir;
421
	}
422
423
	/**
424
	 * @return Folder
425
	 */
426
	private function getSystemTemplateDir() {
427
		$path = 'appdata_' . $this->config->getSystemValue('instanceid', null) . '/richdocuments/templates';
428
		return $this->rootFolder->get($path);
429
	}
430
431
	/**
432
	 * @return Folder
433
	 */
434
	private function getEmptyTemplateDir() {
435
		$path = 'appdata_' . $this->config->getSystemValue('instanceid', null) . '/richdocuments/empty_templates';
436
		return $this->rootFolder->get($path);
437
	}
438
439
	/**
440
	 * Format template file for json return object
441
	 *
442
	 * @param File $template
443
	 * @return array
444
	 */
445
	public function formatNodeReturn(File $template) {
446
		$ooxml = $this->config->getAppValue($this->appName, 'doc_format', '') === 'ooxml';
447
		$documentType = $this->flipTypes()[$template->getMimeType()];
448
		return [
449
			'id'        => $template->getId(),
450
			'name'      => $template->getName(),
451
			'preview'   => $this->urlGenerator->linkToRouteAbsolute('richdocuments.templates.getPreview', ['fileId' => $template->getId()]),
452
			'type'      => $this->flipTypes()[$template->getMimeType()],
453
			'delete'    => $this->urlGenerator->linkToRouteAbsolute('richdocuments.templates.delete', ['fileId' => $template->getId()]),
454
			'extension' => $ooxml ? self::TYPE_EXTENSION_OOXML[$documentType] : self::TYPE_EXTENTION[$documentType],
455
		];
456
	}
457
458
	public function isTemplate($fileId) {
459
		$empty = $this->getEmpty();
460
		$system = $this->getSystem();
461
		$user = $this->getUser();
462
		/** @var File[] $all */
463
		$all = array_merge($empty, $system, $user);
464
465
		foreach ($all as $template) {
466
			if ($template->getId() === $fileId) {
467
				return true;
468
			}
469
		}
470
471
		return false;
472
	}
473
474
	public function formatEmpty(File $template) {
475
		$ooxml = $this->config->getAppValue($this->appName, 'doc_format', '') === 'ooxml';
476
		$documentType = $this->flipTypes()[$template->getMimeType()];
477
		return [
478
			'id'        => $template->getId(),
479
			'name'      => $this->l->t('Empty'),
480
			'type'      => $this->flipTypes()[$template->getMimeType()],
481
			'extension' => $ooxml ? self::TYPE_EXTENSION_OOXML[$documentType] : self::TYPE_EXTENTION[$documentType],
482
		];
483
	}
484
485
	public function isValidTemplateMime($mime, $type = null) {
486
		if ($type === null) {
487
			$allMimes = array_merge(self::$tplTypes['document'], self::$tplTypes['spreadsheet'], self::$tplTypes['presentation']);
488
			if (!in_array($mime, $allMimes)) {
489
				return false;
490
			}
491
		}
492
493
		if ($type !== null && !in_array($mime, self::$tplTypes[$type])) {
494
			return false;
495
		}
496
497
		return true;
498
	}
499
}
500