Passed
Push — master ( 235c52...927bab )
by Daniel
30:30 queued 10s
created

Theme::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 3
c 1
b 0
f 1
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 10
cc 1
nc 1
nop 2
crap 1
1
<?php
2
/**
3
 * CMS Pico - Create websites using Pico CMS for Nextcloud.
4
 *
5
 * @copyright Copyright (c) 2019, Daniel Rudolf (<[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
declare(strict_types=1);
24
25
namespace OCA\CMSPico\Model;
26
27
use OCA\CMSPico\Exceptions\ThemeNotCompatibleException;
28
use OCA\CMSPico\Files\FolderInterface;
29
use OCA\CMSPico\Pico;
30
use OCA\CMSPico\Service\MiscService;
31
use OCP\Files\InvalidPathException;
32
use OCP\Files\NotFoundException;
33
use OCP\Files\NotPermittedException;
34
use Symfony\Component\Yaml\Exception\ParseException as YamlParseException;
35
use Symfony\Component\Yaml\Parser as YamlParser;
36
37
class Theme implements \JsonSerializable
38
{
39
	/** @var int */
40
	const TYPE_SYSTEM = 1;
41
42
	/** @var int */
43
	const TYPE_CUSTOM = 2;
44
45
	/** @var int[] */
46
	const THEME_API_VERSIONS = [
47
		Pico::API_VERSION_0,
48
		Pico::API_VERSION_1,
49
		Pico::API_VERSION_2,
50
		Pico::API_VERSION_3,
51
	];
52
53
	/** @var MiscService */
54
	private $miscService;
55
56
	/** @var FolderInterface */
57
	private $folder;
58
59
	/** @var int */
60
	private $type;
61
62
	/** @var bool|null */
63
	private $compat;
64
65
	/** @var ThemeNotCompatibleException|null */
66
	private $compatException;
67
68
	/**
69
	 * Theme constructor.
70
	 *
71
	 * @param FolderInterface $folder
72
	 * @param int             $type
73
	 */
74 1
	public function __construct(FolderInterface $folder, int $type = self::TYPE_SYSTEM)
75
	{
76 1
		$this->miscService = \OC::$server->query(MiscService::class);
77
78 1
		$this->folder = $folder;
79 1
		$this->type = $type;
80 1
	}
81
82
	/**
83
	 * @return string
84
	 */
85 1
	public function getName(): string
86
	{
87 1
		return $this->folder->getName();
88
	}
89
90
	/**
91
	 * @return FolderInterface
92
	 */
93 1
	public function getFolder(): FolderInterface
94
	{
95 1
		return $this->folder;
96
	}
97
98
	/**
99
	 * @return int
100
	 */
101 1
	public function getType(): int
102
	{
103 1
		return $this->type;
104
	}
105
106
	/**
107
	 * @return bool
108
	 */
109 1
	public function isCompatible(): bool
110
	{
111 1
		if ($this->compat !== null) {
112 1
			return $this->compat;
113
		}
114
115
		try {
116 1
			$this->checkCompatibility();
117 1
			return true;
118
		} catch (ThemeNotCompatibleException $e) {
119
			return false;
120
		}
121
	}
122
123
	/**
124
	 * @throws ThemeNotCompatibleException
125
	 */
126 1
	public function checkCompatibility()
127
	{
128 1
		if ($this->compat === false) {
129
			throw $this->compatException;
130 1
		} elseif ($this->compat) {
131
			return;
132
		}
133
134
		try {
135
			try {
136 1
				$this->getFolder()->getFile('index.twig');
137
			} catch (\Exception $e) {
138
				/** @noinspection PhpUnhandledExceptionInspection */
139
				$this->miscService->consumeException($e, InvalidPathException::class, NotFoundException::class);
140
141
				throw new ThemeNotCompatibleException(
142
					$this->getName(),
143
					'Incompatible theme: Twig template "{file}" not found.',
144
					[ 'file' => $this->getName() . '/index.twig' ]
145
				);
146
			}
147
148
			try {
149 1
				$themeConfigFile = $this->getFolder()->getFile('pico-theme.yml');
150
				$themeConfigYaml = $themeConfigFile->getContent();
151
152
				$themeConfig = (new YamlParser())->parse($themeConfigYaml);
153
				$themeConfig = is_array($themeConfig) ? $themeConfig : [];
154 1
			} catch (\Exception $e) {
155
				/** @noinspection PhpUnhandledExceptionInspection */
156 1
				$this->miscService->consumeException(
157 1
					$e,
158 1
					NotFoundException::class,
159 1
					InvalidPathException::class,
160 1
					NotPermittedException::class,
161 1
					YamlParseException::class
162
				);
163
164 1
				$themeConfig = [];
165
			}
166
167 1
			$apiVersion = Pico::API_VERSION_0;
168 1
			if (isset($themeConfig['api_version'])) {
169
				if (is_int($themeConfig['api_version']) || preg_match('/^[0-9]+$/', $themeConfig['api_version'])) {
170
					$apiVersion = (int)$themeConfig['api_version'];
171
				}
172
			}
173
174 1
			if (!in_array($apiVersion, static::THEME_API_VERSIONS, true)) {
175
				throw new ThemeNotCompatibleException(
176
					$this->getName(),
177
					'Incompatible theme: Themes for Pico CMS for Nextcloud must use one of the API versions '
178
					. '{compatApiVersions}, but this theme uses API version {apiVersion}.',
179
					[ 'compatApiVersions' => implode(', ', static::THEME_API_VERSIONS), 'apiVersion' => $apiVersion ]
180
				);
181
			}
182
183 1
			$this->compat = true;
184 1
			$this->compatException = null;
185
		} catch (ThemeNotCompatibleException $e) {
186
			$this->compat = false;
187
			$this->compatException = $e;
188
189
			throw $e;
190
		}
191 1
	}
192
193
	/**
194
	 * @return array
195
	 */
196 1
	public function toArray(): array
197
	{
198
		$data = [
199 1
			'name' => $this->getName(),
200 1
			'type' => $this->getType(),
201 1
			'compat' => $this->isCompatible(),
202
		];
203
204 1
		if (!$this->isCompatible() && ($this->compatException !== null)) {
205
			$data['compatReason'] = $this->compatException->getRawReason();
206
			$data['compatReasonData'] = $this->compatException->getRawReasonData();
207
		}
208
209 1
		return $data;
210
	}
211
212
	/**
213
	 * @return array
214
	 */
215 1
	public function jsonSerialize(): array
216
	{
217 1
		return $this->toArray();
218
	}
219
}
220