Passed
Pull Request — master (#77)
by Daniel
24:04
created

Version010000   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 270
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 8
Bugs 1 Features 1
Metric Value
wmc 27
eloc 144
c 8
b 1
f 1
dl 0
loc 270
ccs 0
cts 189
cp 0
rs 10

8 Methods

Rating   Name   Duplication   Size   Complexity  
B changeSchema() 0 66 5
A checkComposer() 0 15 3
A migrateCustomPlugins() 0 15 3
A postSchemaChange() 0 7 1
A migrateCustomThemes() 0 15 3
A migratePrivateWebsites() 0 31 5
B createPublicFolder() 0 45 6
A __construct() 0 8 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\Migration;
26
27
use Doctrine\DBAL\Schema\SchemaException;
28
use OC\Encryption\Manager as EncryptionManager;
29
use OCA\CMSPico\AppInfo\Application;
30
use OCA\CMSPico\Db\CoreRequestBuilder;
31
use OCA\CMSPico\Exceptions\ComposerException;
32
use OCA\CMSPico\Exceptions\FilesystemNotWritableException;
33
use OCA\CMSPico\Model\Plugin;
34
use OCA\CMSPico\Model\Theme;
35
use OCA\CMSPico\Model\WebsiteCore;
36
use OCA\CMSPico\Service\ConfigService;
37
use OCA\CMSPico\Service\FileService;
38
use OCA\CMSPico\Service\MiscService;
39
use OCA\CMSPico\Service\PicoService;
40
use OCP\DB\ISchemaWrapper;
41
use OCP\Files\AlreadyExistsException;
42
use OCP\Files\InvalidPathException;
43
use OCP\Files\NotPermittedException;
44
use OCP\IDBConnection;
45
use OCP\IL10N;
46
use OCP\Migration\IOutput;
47
use OCP\Migration\SimpleMigrationStep;
48
49
class Version010000 extends SimpleMigrationStep
50
{
51
	/** @var IDBConnection */
52
	private $databaseConnection;
53
54
	/** @var IL10N */
55
	private $l10n;
56
57
	/** @var EncryptionManager */
58
	private $encryptionManager;
59
60
	/** @var ConfigService */
61
	private $configService;
62
63
	/** @var FileService */
64
	private $fileService;
65
66
	/** @var MiscService */
67
	private $miscService;
68
69
	/**
70
	 * Version010000 constructor.
71
	 */
72
	public function __construct()
73
	{
74
		$this->databaseConnection = \OC::$server->getDatabaseConnection();
75
		$this->l10n = \OC::$server->getL10N(Application::APP_NAME);
76
		$this->encryptionManager = \OC::$server->getEncryptionManager();
77
		$this->configService = \OC::$server->query(ConfigService::class);
78
		$this->fileService = \OC::$server->query(FileService::class);
79
		$this->miscService = \OC::$server->query(MiscService::class);
80
	}
81
82
	/**
83
	 * @param IOutput  $output
84
	 * @param \Closure $schemaClosure
85
	 * @param array    $options
86
	 *
87
	 * @return ISchemaWrapper
88
	 */
89
	public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options): ISchemaWrapper
90
	{
91
		/** @var ISchemaWrapper $schema */
92
		$schema = $schemaClosure();
93
94
		try {
95
			$table = $schema->getTable(CoreRequestBuilder::TABLE_WEBSITES);
96
		} catch (SchemaException $e) {
97
			$table = $schema->createTable(CoreRequestBuilder::TABLE_WEBSITES);
98
99
			$table->addColumn('id', 'integer', [
100
				'autoincrement' => true,
101
				'notnull' => true,
102
				'length' => 4,
103
				'unsigned' => true,
104
			]);
105
			$table->addColumn('user_id', 'string', [
106
				'notnull' => true,
107
				'length' => 64,
108
			]);
109
			$table->addColumn('name', 'string', [
110
				'notnull' => true,
111
				'length' => 255,
112
			]);
113
			$table->addColumn('site', 'string', [
114
				'notnull' => true,
115
				'length' => 255,
116
			]);
117
			$table->addColumn('theme', 'string', [
118
				'notnull' => true,
119
				'length' => 64,
120
				'default' => 'default',
121
			]);
122
			$table->addColumn('type', 'smallint', [
123
				'notnull' => true,
124
				'length' => 1,
125
			]);
126
			$table->addColumn('options', 'string', [
127
				'notnull' => false,
128
				'length' => 255,
129
			]);
130
			$table->addColumn('path', 'string', [
131
				'notnull' => false,
132
				'length' => 255,
133
			]);
134
			$table->addColumn('creation', 'datetime', [
135
				'notnull' => false,
136
			]);
137
138
			$table->setPrimaryKey(['id']);
139
		}
140
141
		$themeColumn = $table->getColumn('theme');
142
		if ($themeColumn->getLength() < 64) {
143
			$themeColumn->setLength(64);
144
		}
145
146
		if (!$table->hasIndex('user_id')) {
147
			$table->addIndex([ 'user_id' ], 'user_id');
148
		}
149
150
		if (!$table->hasIndex('site')) {
151
			$table->addIndex([ 'site' ], 'site');
152
		}
153
154
		return $schema;
155
	}
156
157
	/**
158
	 * @param IOutput  $output
159
	 * @param \Closure $schemaClosure
160
	 * @param array    $options
161
	 */
162
	public function postSchemaChange(IOutput $output, \Closure $schemaClosure, array $options)
163
	{
164
		$this->migratePrivateWebsites();
165
		$this->checkComposer();
166
		$this->createPublicFolder();
167
		$this->migrateCustomThemes();
168
		$this->migrateCustomPlugins();
169
	}
170
171
	/**
172
	 * @return void
173
	 */
174
	private function migratePrivateWebsites()
175
	{
176
		$qbUpdate = $this->databaseConnection->getQueryBuilder();
177
		$qbUpdate
178
			->update(CoreRequestBuilder::TABLE_WEBSITES, 'w')
179
			->set('w.type', $qbUpdate->createParameter('type'))
180
			->set('w.options', $qbUpdate->createParameter('options'))
181
			->where($qbUpdate->expr()->eq('w.id', $qbUpdate->createParameter('id')));
182
183
		$selectCursor = $this->databaseConnection->getQueryBuilder()
184
			->select('w.id', 'w.type', 'w.options')
185
			->from(CoreRequestBuilder::TABLE_WEBSITES, 'w')
186
			->execute();
187
188
		while ($data = $selectCursor->fetch()) {
189
			$websiteOptions = $data['options'] ? json_decode($data['options'], true) : [];
190
			if (isset($websiteOptions['private'])) {
191
				$websiteType = $websiteOptions['private'] ? WebsiteCore::TYPE_PRIVATE : WebsiteCore::TYPE_PUBLIC;
192
				unset($websiteOptions['private']);
193
194
				$qbUpdate->setParameters([
195
					'id' => $data['id'],
196
					'type' => $websiteType,
197
					'options' => json_encode($websiteOptions)
198
				]);
199
200
				$qbUpdate->execute();
201
			}
202
		}
203
204
		$selectCursor->closeCursor();
205
	}
206
207
	/**
208
	 * @throws ComposerException
209
	 */
210
	private function checkComposer()
211
	{
212
		$appPath = Application::getAppPath();
213
		if (!is_file($appPath . '/vendor/autoload.php')) {
214
			try {
215
				$relativeAppPath = $this->miscService->getRelativePath($appPath) . '/';
216
			} catch (InvalidPathException $e) {
217
				$relativeAppPath = 'apps/' . Application::APP_NAME . '/';
218
			}
219
220
			throw new ComposerException($this->l10n->t(
221
				'Failed to enable Pico CMS for Nextcloud: Couldn\'t find "%s". Make sure to install the app\'s '
222
						. 'dependencies by executing `composer install` in the app\'s install directory below "%s". '
223
						. 'Then try again enabling Pico CMS for Nextcloud.',
224
				[ $relativeAppPath . 'vendor/autoload.php', $relativeAppPath ]
225
			));
226
		}
227
	}
228
229
	/**
230
	 * @throws FilesystemNotWritableException
231
	 */
232
	private function createPublicFolder()
233
	{
234
		$publicFolder = $this->fileService->getPublicFolder();
235
236
		try {
237
			try {
238
				$publicThemesFolder = $publicFolder->newFolder(PicoService::DIR_THEMES);
239
			} catch (AlreadyExistsException $e) {
240
				$publicThemesFolder = $publicFolder->getFolder(PicoService::DIR_THEMES);
241
			}
242
243
			$publicThemesTestFileName = $this->miscService->getRandom(10, 'tmp', Application::APP_NAME . '-test');
244
			$publicThemesTestFile = $publicThemesFolder->newFile($publicThemesTestFileName);
245
			$publicThemesTestFile->delete();
246
247
			try {
248
				$publicPluginsFolder = $publicFolder->newFolder(PicoService::DIR_PLUGINS);
249
			} catch (AlreadyExistsException $e) {
250
				$publicPluginsFolder = $publicFolder->getFolder(PicoService::DIR_PLUGINS);
251
			}
252
253
			$publicPluginsTestFileName = $this->miscService->getRandom(10, 'tmp', Application::APP_NAME . '-test');
254
			$publicPluginsTestFile = $publicPluginsFolder->newFile($publicPluginsTestFileName);
255
			$publicPluginsTestFile->delete();
256
		} catch (NotPermittedException $e) {
257
			try {
258
				$appDataPublicPath = Application::getAppPath() . '/appdata_public';
259
				$appDataPublicPath = $this->miscService->getRelativePath($appDataPublicPath) . '/';
260
			} catch (InvalidPathException $e) {
261
				$appDataPublicPath = 'apps/' . Application::APP_NAME . '/appdata_public/';
262
			}
263
264
			try {
265
				$dataPath = $this->configService->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data');
266
				$dataPath = $this->miscService->getRelativePath($dataPath) . '/';
267
			} catch (InvalidPathException $e) {
268
				$dataPath = 'data/';
269
			}
270
271
			throw new FilesystemNotWritableException($this->l10n->t(
272
				'Failed to enable Pico CMS for Nextcloud: The webserver has no permission to create files and '
273
						. 'folders below "%s". Make sure to give the webserver write access to this directory by '
274
						. 'changing its permissions and ownership to the same as of your "%s" directory. Then try '
275
						. 'again enabling Pico CMS for Nextcloud.',
276
				[ $appDataPublicPath, $dataPath ]
277
			));
278
		}
279
	}
280
281
	/**
282
	 * @return void
283
	 */
284
	private function migrateCustomThemes()
285
	{
286
		$customThemesJson = $this->configService->getAppValue(ConfigService::CUSTOM_THEMES);
287
		$customThemes = $customThemesJson ? json_decode($customThemesJson, true) : [];
288
289
		$newCustomThemes = [];
290
		foreach ($customThemes as $themeName) {
291
			$newCustomThemes[$themeName] = [
292
				'name' => $themeName,
293
				'type' => Theme::THEME_TYPE_CUSTOM,
294
				'compat' => true
295
			];
296
		}
297
298
		$this->configService->setAppValue(ConfigService::CUSTOM_THEMES, json_encode($newCustomThemes));
299
	}
300
301
	/**
302
	 * @return void
303
	 */
304
	private function migrateCustomPlugins()
305
	{
306
		$customPluginsJson = $this->configService->getAppValue(ConfigService::CUSTOM_PLUGINS);
307
		$customPlugins = $customPluginsJson ? json_decode($customPluginsJson, true) : [];
308
309
		$newCustomPlugins = [];
310
		foreach ($customPlugins as $pluginName) {
311
			$newCustomPlugins[$pluginName] = [
312
				'name' => $pluginName,
313
				'type' => Plugin::PLUGIN_TYPE_CUSTOM,
314
				'compat' => true
315
			];
316
		}
317
318
		$this->configService->setAppValue(ConfigService::CUSTOM_PLUGINS, json_encode($newCustomPlugins));
319
	}
320
}
321