1<?php
2/**
3 * Matomo - free/libre analytics platform
4 *
5 * @link https://matomo.org
6 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
7 */
8
9namespace Piwik\Container;
10
11use DI\Container;
12use DI\ContainerBuilder;
13use Piwik\Application\Kernel\GlobalSettingsProvider;
14use Piwik\Application\Kernel\PluginList;
15use Piwik\Plugin\Manager;
16
17/**
18 * Creates a configured DI container.
19 */
20class ContainerFactory
21{
22    /**
23     * @var PluginList
24     */
25    private $pluginList;
26
27    /**
28     * @var GlobalSettingsProvider
29     */
30    private $settings;
31
32    /**
33     * Optional environment configs to load.
34     *
35     * @var string[]
36     */
37    private $environments;
38
39    /**
40     * @var array[]
41     */
42    private $definitions;
43
44    /**
45     * @param PluginList $pluginList
46     * @param GlobalSettingsProvider $settings
47     * @param string[] $environment Optional environment configs to load.
48     * @param array[] $definitions
49     */
50    public function __construct(PluginList $pluginList, GlobalSettingsProvider $settings, array $environments = array(), array $definitions = array())
51    {
52        $this->pluginList = $pluginList;
53        $this->settings = $settings;
54        $this->environments = $environments;
55        $this->definitions = $definitions;
56    }
57
58    /**
59     * @link http://php-di.org/doc/container-configuration.html
60     * @throws \Exception
61     * @return Container
62     */
63    public function create()
64    {
65        $builder = new ContainerBuilder();
66
67        $builder->useAnnotations(false);
68
69        // INI config
70        $builder->addDefinitions(new IniConfigDefinitionSource($this->settings));
71
72        // Global config
73        $builder->addDefinitions(PIWIK_DOCUMENT_ROOT . '/config/global.php');
74
75        // Plugin configs
76        $this->addPluginConfigs($builder);
77
78        // Development config
79        if ($this->isDevelopmentModeEnabled()) {
80            $this->addEnvironmentConfig($builder, 'dev');
81        }
82
83        // Environment config
84        foreach ($this->environments as $environment) {
85            $this->addEnvironmentConfig($builder, $environment);
86        }
87
88        // User config
89        if (file_exists(PIWIK_USER_PATH . '/config/config.php')
90            && !in_array('test', $this->environments, true)) {
91            $builder->addDefinitions(PIWIK_USER_PATH . '/config/config.php');
92        }
93
94        if (!empty($this->definitions)) {
95            foreach ($this->definitions as $definitionArray) {
96                $builder->addDefinitions($definitionArray);
97            }
98        }
99
100        $container = $builder->build();
101        $container->set('Piwik\Application\Kernel\PluginList', $this->pluginList);
102        $container->set('Piwik\Application\Kernel\GlobalSettingsProvider', $this->settings);
103
104        return $container;
105    }
106
107    private function addEnvironmentConfig(ContainerBuilder $builder, $environment)
108    {
109        if (!$environment) {
110            return;
111        }
112
113        $file = sprintf('%s/config/environment/%s.php', PIWIK_USER_PATH, $environment);
114
115        if (file_exists($file)) {
116            $builder->addDefinitions($file);
117        }
118
119        // add plugin environment configs
120        $plugins = $this->pluginList->getActivatedPlugins();
121
122        if ($this->shouldSortPlugins()) {
123            $plugins = $this->sortPlugins($plugins);
124        }
125
126        foreach ($plugins as $plugin) {
127            $baseDir = Manager::getPluginDirectory($plugin);
128
129            $environmentFile = $baseDir . '/config/' . $environment . '.php';
130            if (file_exists($environmentFile)) {
131                $builder->addDefinitions($environmentFile);
132            }
133        }
134    }
135
136    private function addPluginConfigs(ContainerBuilder $builder)
137    {
138        $plugins = $this->pluginList->getActivatedPlugins();
139
140        if ($this->shouldSortPlugins()) {
141            $plugins = $this->sortPlugins($plugins);
142        }
143
144        foreach ($plugins as $plugin) {
145            $baseDir = Manager::getPluginDirectory($plugin);
146
147            $file = $baseDir . '/config/config.php';
148            if (file_exists($file)) {
149                $builder->addDefinitions($file);
150            }
151        }
152    }
153
154    /**
155     * This method is required for Matomo Cloud to allow for custom sorting of plugin order
156     *
157     * @return bool
158     */
159    private function shouldSortPlugins()
160    {
161        return isset($GLOBALS['MATOMO_SORT_PLUGINS']) && is_callable($GLOBALS['MATOMO_SORT_PLUGINS']);
162    }
163
164    /**
165     * @param array $plugins
166     * @return array
167     */
168    private function sortPlugins(array $plugins)
169    {
170        return call_user_func($GLOBALS['MATOMO_SORT_PLUGINS'], $plugins);
171    }
172
173    private function isDevelopmentModeEnabled()
174    {
175        $section = $this->settings->getSection('Development');
176        return (bool) @$section['enabled']; // TODO: code redundancy w/ Development. hopefully ok for now.
177    }
178}
179