1<?php
2/* Copyright (c) 2016 Richard Klees <richard.klees@concepts-and-training.de> Extended GPL, see docs/LICENSE */
3
4namespace ILIAS\Setup\CLI;
5
6use ILIAS\Setup\UnachievableException;
7use ILIAS\Setup\NoConfirmationException;
8use ILIAS\Setup\Agent;
9use ILIAS\Setup\Objective;
10use ILIAS\Setup\Environment;
11use ILIAS\Setup\Config;
12use ILIAS\Setup\ObjectiveIterator;
13use ILIAS\Setup\ObjectiveWithPreconditions;
14use Symfony\Component\Console\Command\Command;
15use Symfony\Component\Console\Input\InputInterface;
16use Symfony\Component\Console\Input\InputArgument;
17use Symfony\Component\Console\Input\InputOption;
18use Symfony\Component\Console\Output\OutputInterface;
19
20/**
21 * Command base class.
22 */
23abstract class BaseCommand extends Command
24{
25    protected static $defaultName = "install";
26
27    /**
28     * @var callable
29     */
30    protected $lazy_agent;
31
32    /**
33     * @var Agent|null
34     */
35    protected $agent;
36
37    /**
38     * @var ConfigReader
39     */
40    protected $config_reader;
41
42    /**
43     * var Objective[]
44     */
45    protected $preconditions;
46
47    /**
48     * @var callable $lazy_agent must return a Setup\Agent
49     * @var Objective[] $preconditions will be achieved before command invocation
50     */
51    public function __construct(callable $lazy_agent, ConfigReader $config_reader, array $preconditions)
52    {
53        parent::__construct();
54        $this->lazy_agent = $lazy_agent;
55        $this->agent = null;
56        $this->config_reader = $config_reader;
57        $this->preconditions = $preconditions;
58    }
59
60    protected function getAgent() : Agent
61    {
62        if ($this->agent !== null) {
63            return $this->agent;
64        }
65        $this->agent = ($this->lazy_agent)();
66        return $this->agent;
67    }
68
69    public function configure()
70    {
71        $this
72            ->addArgument("config", InputArgument::REQUIRED, "Configuration file for the Setup")
73            ->addOption("config", null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, "Define fields in the configuration file that should be overwritten, e.g. \"a.b.c=foo\"", [])
74            ->addOption("yes", "y", InputOption::VALUE_NONE, "Confirm every message of the setup.");
75    }
76
77    public function execute(InputInterface $input, OutputInterface $output)
78    {
79        $io = new IOWrapper($input, $output, $this->shouldSayYes($input));
80
81        $this->printLicenseMessage($io, $input);
82
83        $this->printIntroMessage($io);
84
85        $config = $this->readAgentConfig($this->getAgent(), $input);
86        $environment = $this->buildEnvironment($this->getAgent(), $config, $io);
87        $goal = $this->getObjective($this->getAgent(), $config);
88        if (count($this->preconditions) > 0) {
89            $goal = new ObjectiveWithPreconditions(
90                $goal,
91                ...$this->preconditions
92            );
93        }
94        $goals = new ObjectiveIterator($environment, $goal);
95
96        try {
97            while ($goals->valid()) {
98                $current = $goals->current();
99                $io->startObjective($current->getLabel(), $current->isNotable());
100                try {
101                    $environment = $current->achieve($environment);
102                    $io->finishedLastObjective($current->getLabel(), $current->isNotable());
103                    $goals->setEnvironment($environment);
104                } catch (UnachievableException $e) {
105                    $goals->markAsFailed($current);
106                    $io->error($e->getMessage());
107                    $io->failedLastObjective($current->getLabel());
108                }
109                $goals->next();
110            }
111            $this->printOutroMessage($io);
112        } catch (NoConfirmationException $e) {
113            $io->error("Aborting Setup, a necessary confirmation is missing:\n\n" . $e->getRequestedConfirmation());
114        }
115    }
116
117    protected function shouldSayYes(InputInterface $input) : bool
118    {
119        return $input->getOption("yes") ?? false;
120    }
121
122    protected function printLicenseMessage(IOWrapper $io, InputInterface $input) : void
123    {
124        if ($this->shouldSayYes($input) || ($input->hasOption("no-interaction") && $input->getOption("no-interaction"))) {
125            return;
126        }
127        $io->text(
128            "   ILIAS Copyright (C) 1998-2019 ILIAS Open Source e.V. - GPLv3\n\n" .
129            "This program comes with ABSOLUTELY NO WARRANTY. This is free software,\n" .
130            "and you are welcome to redistribute it under certain conditions. Look\n" .
131            "into the LICENSE file for details."
132        );
133    }
134
135    abstract protected function printIntroMessage(IOWrapper $io);
136
137    abstract protected function printOutroMessage(IOWrapper $io);
138
139    protected function readAgentConfig(Agent $agent, InputInterface $input) : ?Config
140    {
141        if (!$agent->hasConfig()) {
142            return null;
143        }
144
145        $config_file = $input->getArgument("config");
146        $config_overwrites_raw = $input->getOption("config");
147        $config_overwrites = [];
148        foreach ($config_overwrites_raw as $o) {
149            list($k, $v) = explode("=", $o);
150            $config_overwrites[$k] = $v;
151        }
152        $config_content = $this->config_reader->readConfigFile($config_file, $config_overwrites);
153        $trafo = $this->agent->getArrayToConfigTransformation();
154        return $trafo->transform($config_content);
155    }
156
157    abstract protected function buildEnvironment(Agent $agent, ?Config $config, IOWrapper $io);
158
159    abstract protected function getObjective(Agent $agent, ?Config $config) : Objective;
160}
161