Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 231 |
|
0.00% |
0 / 7 |
CRAP | |
0.00% |
0 / 1 |
tao_install_Setup | |
0.00% |
0 / 231 |
|
0.00% |
0 / 7 |
5852 | |
0.00% |
0 / 1 |
__invoke | |
0.00% |
0 / 157 |
|
0.00% |
0 / 1 |
2756 | |||
prepareParameters | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
30 | |||
resolveParameter | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
20 | |||
wrapPersistenceConfig | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
12 | |||
getCommandLineParameters | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
90 | |||
isMasterSlaveConnection | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
getConfigurationMarkers | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | /** |
4 | * This program is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU General Public License |
6 | * as published by the Free Software Foundation; under version 2 |
7 | * of the License (non-upgradable). |
8 | * |
9 | * This program is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | * GNU General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU General Public License |
15 | * along with this program; if not, write to the Free Software |
16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
17 | * |
18 | * Copyright (c) 2014-2021 (original work) Open Assessment Technologies SA; |
19 | * |
20 | */ |
21 | |
22 | declare(strict_types=1); |
23 | |
24 | use oat\generis\persistence\PersistenceManager; |
25 | use oat\oatbox\action\Action; |
26 | use oat\oatbox\log\ContainerLoggerTrait; |
27 | use oat\oatbox\log\logger\TaoLog; |
28 | use oat\oatbox\log\LoggerService; |
29 | use oat\oatbox\service\ConfigurableService; |
30 | use oat\oatbox\service\ServiceManager; |
31 | use oat\tao\model\configurationMarkers\ConfigurationMarkers; |
32 | use oat\tao\model\configurationMarkers\SerializableSecretDtoFactory; |
33 | use oat\tao\model\service\InjectionAwareService; |
34 | use Pimple\Container; |
35 | use Zend\ServiceManager\ServiceLocatorAwareInterface; |
36 | |
37 | // phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace, Squiz.Classes.ValidClassName.NotCamelCaps |
38 | class tao_install_Setup implements Action |
39 | { |
40 | // Adding container and logger. |
41 | use ContainerLoggerTrait; |
42 | |
43 | /** |
44 | * Setup related dependencies will be reached under this offset. |
45 | */ |
46 | public const CONTAINER_INDEX = 'taoInstallSetup'; |
47 | |
48 | /** |
49 | * The setup json content offset in the container. |
50 | */ |
51 | private const SETUP_JSON_CONTENT_OFFSET = 'setupJsonContentOffset'; |
52 | |
53 | /** |
54 | * @param mixed $params The setup params. |
55 | * |
56 | * @throws ErrorException When a module is missing or other kind of general error. |
57 | * @throws common_Exception When the presented config file does not exist |
58 | * @throws common_exception_Error |
59 | * @throws common_ext_ExtensionException When a presented parameter is invalid or malformed. |
60 | * @throws InvalidArgumentException |
61 | * @throws tao_install_utils_Exception |
62 | */ |
63 | public function __invoke($params) |
64 | { |
65 | // Using the container if it's necessary with automatic dependency returning. |
66 | $params = $this->initContainer($params, static::CONTAINER_INDEX); |
67 | |
68 | $this->logNotice('Installing TAO...'); |
69 | |
70 | if ($this->getContainer() !== null && $this->getContainer()->offsetExists(static::SETUP_JSON_CONTENT_OFFSET)) { |
71 | $parameters = json_decode($this->getContainer()->offsetGet(static::SETUP_JSON_CONTENT_OFFSET), true); |
72 | if (is_null($parameters)) { |
73 | throw new InvalidArgumentException('Your Setup JSON seed is malformed'); |
74 | } |
75 | } else { |
76 | if (!isset($params[0])) { |
77 | throw new InvalidArgumentException('You should provide a file path'); |
78 | } |
79 | |
80 | $filePath = $params[0]; |
81 | |
82 | if (!file_exists($filePath)) { |
83 | throw new ErrorException('Unable to find ' . $filePath); |
84 | } |
85 | |
86 | $info = pathinfo($filePath); |
87 | |
88 | switch ($info['extension']) { |
89 | case 'json': |
90 | $parameters = json_decode(file_get_contents($filePath), true); |
91 | if (is_null($parameters)) { |
92 | throw new InvalidArgumentException('Your JSON file is malformed'); |
93 | } |
94 | break; |
95 | case 'yml': |
96 | if (extension_loaded('yaml')) { |
97 | $parameters = yaml_parse_file($filePath); |
98 | if ($parameters === false) { |
99 | throw new InvalidArgumentException('Your YAML file is malformed'); |
100 | } |
101 | } else { |
102 | throw new ErrorException('Extension yaml should be installed'); |
103 | } |
104 | break; |
105 | default: |
106 | throw new InvalidArgumentException('Please provide a JSON or YAML file'); |
107 | } |
108 | } |
109 | |
110 | /** @var LoggerService $loggerService */ |
111 | $loggerService = $this->getContainer()->offsetGet(LoggerService::SERVICE_ID); |
112 | $loggerService->addLogger( |
113 | new TaoLog([ |
114 | 'appenders' => [ |
115 | [ |
116 | 'class' => 'SingleFileAppender', |
117 | 'threshold' => common_Logger::TRACE_LEVEL, |
118 | 'file' => TAO_INSTALL_PATH . 'tao/install/log/install.log' |
119 | ] |
120 | ] |
121 | ]) |
122 | ); |
123 | |
124 | $options = [ |
125 | "install_sent" => "1" |
126 | , "module_host" => "tao.local" |
127 | , "module_lang" => "en-US" |
128 | , "module_mode" => "debug" |
129 | , "module_name" => "mytao" |
130 | , "module_namespace" => "" |
131 | , "module_url" => "" |
132 | , "submit" => "Install" |
133 | , "user_email" => "" |
134 | , "user_firstname" => "" |
135 | , "user_lastname" => "" |
136 | , "user_login" => "" |
137 | , "user_pass" => "" |
138 | , "instance_name" => null |
139 | , "extensions" => null |
140 | , 'timezone' => date_default_timezone_get() |
141 | , 'extra_persistences' => [] |
142 | ]; |
143 | |
144 | if (!isset($parameters['configuration'])) { |
145 | throw new InvalidArgumentException('Your config should have a \'configuration\' key'); |
146 | } |
147 | |
148 | if (!isset($parameters['configuration']['generis'])) { |
149 | throw new InvalidArgumentException('Your config should have a \'generis\' key under \'configuration\''); |
150 | } |
151 | |
152 | if (!isset($parameters['configuration']['global'])) { |
153 | throw new InvalidArgumentException('Your config should have a \'global\' key under \'configuration\''); |
154 | } |
155 | |
156 | $global = $parameters['configuration']['global']; |
157 | |
158 | $markers = $this->getConfigurationMarkers(); |
159 | |
160 | $global = $markers->replaceMarkers($global); |
161 | $options['module_namespace'] = $global['namespace']; |
162 | $options['instance_name'] = $global['instance_name']; |
163 | $options['module_url'] = $global['url']; |
164 | $options['module_lang'] = $global['lang']; |
165 | $options['module_mode'] = $global['mode']; |
166 | $options['timezone'] = $global['timezone']; |
167 | $options['import_local'] = (isset($global['import_data']) && $global['import_data'] === true); |
168 | |
169 | $rootDir = dir(dirname(__FILE__) . '/../../'); |
170 | $options['root_path'] = $global['root_path'] ?? realpath($rootDir->path) . DIRECTORY_SEPARATOR; |
171 | |
172 | $options['file_path'] = $global['file_path'] ?? $options['root_path'] . 'data' . DIRECTORY_SEPARATOR; |
173 | |
174 | if (isset($global['session_name'])) { |
175 | $options['session_name'] = $global['session_name']; |
176 | } |
177 | |
178 | if (isset($global['anonymous_lang'])) { |
179 | $options['anonymous_lang'] = $global['anonymous_lang']; |
180 | } |
181 | |
182 | //get extensions to install |
183 | if (isset($parameters['extensions'])) { |
184 | $options['extensions'] = $parameters['extensions']; |
185 | } |
186 | |
187 | if (!isset($parameters['super-user'])) { |
188 | throw new InvalidArgumentException('Your config should have a \'global\' key under \'generis\''); |
189 | } |
190 | |
191 | $superUser = $parameters['super-user']; |
192 | $options['user_login'] = $superUser['login']; |
193 | $options['user_pass1'] = $superUser['password']; |
194 | if (isset($parameters['lastname'])) { |
195 | $options['user_lastname'] = $parameters['lastname']; |
196 | } |
197 | if (isset($parameters['firstname'])) { |
198 | $options['user_firstname'] = $parameters['firstname']; |
199 | } |
200 | if (isset($parameters['email'])) { |
201 | $options['user_email'] = $parameters['email']; |
202 | } |
203 | |
204 | |
205 | $installOptions = [ |
206 | 'root_path' => $options['root_path'], |
207 | 'install_path' => $options['root_path'] . 'tao/install/', |
208 | ]; |
209 | |
210 | if (isset($global['installation_config_path'])) { |
211 | $installOptions['installation_config_path'] = $global['installation_config_path']; |
212 | } |
213 | |
214 | // run the actual install |
215 | if ($this->getContainer() instanceof Container) { |
216 | $this->getContainer()->offsetSet(tao_install_Installator::CONTAINER_INDEX, $installOptions); |
217 | $installator = new tao_install_Installator($this->getContainer()); |
218 | } else { |
219 | $installator = new tao_install_Installator($installOptions); |
220 | } |
221 | |
222 | $serviceManager = $installator->getServiceManager(); |
223 | |
224 | if (!isset($parameters['configuration']['generis']['persistences'])) { |
225 | throw new InvalidArgumentException('Your config should have a \'persistence\' key under \'generis\''); |
226 | } |
227 | $persistences = $parameters['configuration']['generis']['persistences']; |
228 | if (isset($persistences['default'])) { |
229 | $parameters['configuration']['generis']['persistences'] = $this->wrapPersistenceConfig($persistences); |
230 | } elseif (!isset($persistences['type'])) { |
231 | throw new InvalidArgumentException('Your config should have a \'default\' key under \'persistences\''); |
232 | } |
233 | |
234 | //@TODO use $serviceManager->getContainer(ConfigurationMarkers::class) after refactoring taoSetup to use full DI |
235 | $parameters = $markers->replaceMarkers($parameters); |
236 | |
237 | foreach ($parameters['configuration'] as $extension => $configs) { |
238 | foreach ($configs as $key => $config) { |
239 | if (is_array($config) && isset($config['type']) && $config['type'] === 'configurableService') { |
240 | $className = $config['class']; |
241 | $params = $config['options']; |
242 | if (is_a($className, ConfigurableService::class, true)) { |
243 | if (is_a($className, InjectionAwareService::class, true)) { |
244 | $service = new $className( |
245 | ...$this->prepareParameters($className, $params, $serviceManager) |
246 | ); |
247 | } else { |
248 | $service = new $className($params); |
249 | } |
250 | $serviceManager->register($extension . '/' . $key, $service); |
251 | } else { |
252 | $this->logWarning( |
253 | sprintf('The class : %s can not be set as a Configurable Service', $className) |
254 | ); |
255 | $this->logWarning( |
256 | 'Make sure your configuration is correct and all required libraries are installed' |
257 | ); |
258 | } |
259 | } |
260 | } |
261 | } |
262 | |
263 | // mod rewrite cannot be detected in CLI Mode. |
264 | $installator->escapeCheck('custom_tao_ModRewrite'); |
265 | $logger = $this->getLogger(); |
266 | |
267 | $installator->install($options, function () use ($serviceManager, $parameters, $logger) { |
268 | /** @var common_ext_ExtensionsManager $extensionManager */ |
269 | $extensionManager = $serviceManager->get(common_ext_ExtensionsManager::SERVICE_ID); |
270 | foreach ($parameters['configuration'] as $ext => $configs) { |
271 | foreach ($configs as $key => $config) { |
272 | if (! (is_array($config) && isset($config['type']) && $config['type'] === 'configurableService')) { |
273 | if (! is_null($extensionManager->getInstalledVersion($ext))) { |
274 | $extension = $extensionManager->getExtensionById($ext); |
275 | if ( |
276 | !$extension->hasConfig($key) || |
277 | !$extension->getConfig($key) instanceof ConfigurableService |
278 | ) { |
279 | if (! $extension->setConfig($key, $config)) { |
280 | throw new ErrorException( |
281 | sprintf('Your config %s/%s cannot be set', $ext, $key) |
282 | ); |
283 | } |
284 | } |
285 | } |
286 | } |
287 | } |
288 | } |
289 | // execute post install scripts |
290 | if (isset($parameters['postInstall'])) { |
291 | foreach ($parameters['postInstall'] as $script) { |
292 | if (isset($script['class']) && is_a($script['class'], Action::class, true)) { |
293 | $object = new $script['class'](); |
294 | if (is_a($object, ServiceLocatorAwareInterface::class)) { |
295 | $object->setServiceLocator($serviceManager); |
296 | } |
297 | $params = (isset($script['params']) && is_array($script['params'])) ? $script['params'] : []; |
298 | $report = call_user_func($object, $params); |
299 | |
300 | if ($report instanceof common_report_Report) { |
301 | $logger->info(helpers_Report::renderToCommandline($report)); |
302 | } |
303 | } |
304 | } |
305 | } |
306 | $logger->notice('Installation completed!'); |
307 | }); |
308 | } |
309 | |
310 | /** |
311 | * @param string $class |
312 | * @param array $parametersToSort |
313 | * @param ServiceManager $serviceManager |
314 | * |
315 | * @return array |
316 | * @throws ReflectionException |
317 | */ |
318 | private function prepareParameters(string $class, array $parametersToSort, ServiceManager $serviceManager): array |
319 | { |
320 | $reflectionClass = new ReflectionClass($class); |
321 | |
322 | $constructParameters = $reflectionClass->getMethod('__construct')->getParameters(); |
323 | |
324 | $sortedParameters = []; |
325 | |
326 | while ($constructParameters && $parametersToSort) { |
327 | $parameter = array_shift($constructParameters); |
328 | $parameterName = $parameter->getName(); |
329 | |
330 | try { |
331 | $paramValue = $parametersToSort[$parameterName] ?? $parameter->getDefaultValue(); |
332 | |
333 | $sortedParameters[] = $this->resolveParameter($parameter, $paramValue, $serviceManager); |
334 | |
335 | unset($parametersToSort[$parameterName]); |
336 | } catch (ReflectionException $exception) { |
337 | throw new RuntimeException( |
338 | sprintf('No default value for `$%s` argument in %s::__construct', $parameterName, $class) |
339 | ); |
340 | } |
341 | } |
342 | |
343 | if ($parametersToSort) { |
344 | throw new InvalidArgumentException( |
345 | sprintf('Invalid arguments `%s` specified for %s', implode(', ', array_keys($parametersToSort)), $class) |
346 | ); |
347 | } |
348 | |
349 | return $sortedParameters; |
350 | } |
351 | |
352 | private function resolveParameter(ReflectionParameter $parameter, $paramValue, ServiceManager $serviceManager) |
353 | { |
354 | if ( |
355 | is_string($paramValue) |
356 | && $parameter->getClass() !== null |
357 | && $serviceManager->has($paramValue) |
358 | ) { |
359 | $paramValue = $serviceManager->get($paramValue); |
360 | } |
361 | |
362 | return $paramValue; |
363 | } |
364 | |
365 | /** |
366 | * Transforms the seed persistence configuration into command line parameters |
367 | * and then back into a persistence configuration to ensure backwards compatibility |
368 | * with the previous process |
369 | * @param array $persistences |
370 | * @return array |
371 | */ |
372 | private function wrapPersistenceConfig($persistences) |
373 | { |
374 | if ($this->isMasterSlaveConnection($persistences['default'])) { |
375 | $defaultPersistence = [ |
376 | 'driver' => 'dbal', |
377 | 'connection' => $persistences['default']['connection'], |
378 | ]; |
379 | } else { |
380 | $installParams = $this->getCommandLineParameters($persistences['default']); |
381 | |
382 | $dbalConfigCreator = new tao_install_utils_DbalConfigCreator(); |
383 | $defaultPersistence = $dbalConfigCreator->createDbalConfig($installParams); |
384 | } |
385 | |
386 | if (isset($persistences['default']['sqlLoggerClass'])) { |
387 | $defaultPersistence['sqlLoggerClass'] = $persistences['default']['sqlLoggerClass']; |
388 | } |
389 | |
390 | $persistences['default'] = $defaultPersistence; |
391 | |
392 | return [ |
393 | 'type' => 'configurableService', |
394 | 'class' => PersistenceManager::class, |
395 | 'options' => [ |
396 | 'persistences' => $persistences, |
397 | ], |
398 | ]; |
399 | } |
400 | |
401 | private function getCommandLineParameters(array $defaultPersistenceConfig): array |
402 | { |
403 | if (isset($defaultPersistenceConfig['connection'])) { |
404 | $options['db_driver'] = $defaultPersistenceConfig['connection']['driver']; |
405 | |
406 | if (isset($defaultPersistenceConfig['connection']['driverClass'])) { |
407 | $options['db_driverClass'] = $defaultPersistenceConfig['connection']['driverClass']; |
408 | } |
409 | |
410 | if (isset($defaultPersistenceConfig['connection']['driverOptions'])) { |
411 | $options['db_driverOptions'] = $defaultPersistenceConfig['connection']['driverOptions']; |
412 | } |
413 | |
414 | if (isset($defaultPersistenceConfig['connection']['instance'])) { |
415 | $options['db_instance'] = $defaultPersistenceConfig['connection']['instance']; |
416 | } |
417 | |
418 | $options['db_host'] = $defaultPersistenceConfig['connection']['host']; |
419 | $options['db_name'] = $defaultPersistenceConfig['connection']['dbname']; |
420 | |
421 | if (isset($defaultPersistenceConfig['connection']['user'])) { |
422 | $options['db_user'] = $defaultPersistenceConfig['connection']['user']; |
423 | } |
424 | |
425 | if (isset($defaultPersistenceConfig['connection']['password'])) { |
426 | $options['db_pass'] = $defaultPersistenceConfig['connection']['password']; |
427 | } |
428 | } else { |
429 | $options['db_driver'] = $defaultPersistenceConfig['driver']; |
430 | $options['db_host'] = $defaultPersistenceConfig['host']; |
431 | $options['db_name'] = $defaultPersistenceConfig['dbname']; |
432 | |
433 | if (isset($defaultPersistenceConfig['user'])) { |
434 | $options['db_user'] = $defaultPersistenceConfig['user']; |
435 | } |
436 | |
437 | if (isset($defaultPersistenceConfig['password'])) { |
438 | $options['db_pass'] = $defaultPersistenceConfig['password']; |
439 | } |
440 | } |
441 | |
442 | return $options; |
443 | } |
444 | |
445 | private function isMasterSlaveConnection(array $defaultPersistenceConfig): bool |
446 | { |
447 | return |
448 | isset($defaultPersistenceConfig['connection']['wrapperClass']) |
449 | && is_a( |
450 | $defaultPersistenceConfig['connection']['wrapperClass'], |
451 | '\\Doctrine\\DBAL\\Connections\\MasterSlaveConnection', |
452 | true |
453 | ); |
454 | } |
455 | |
456 | private function getConfigurationMarkers(): ConfigurationMarkers |
457 | { |
458 | return new ConfigurationMarkers( |
459 | new SerializableSecretDtoFactory(), |
460 | $this->getLogger() |
461 | ); |
462 | } |
463 | } |