Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 80
0.00% covered (danger)
0.00%
0 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
extension_manager
0.00% covered (danger)
0.00%
0 / 80
0.00% covered (danger)
0.00%
0 / 11
1640
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 pre_install
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 post_install
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
30
 pre_update
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
30
 post_update
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 remove
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 pre_remove
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
56
 start_managing
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
90
 check_requirements
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 set_enable_on_install
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 set_purge_on_remove
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 *
4 * This file is part of the phpBB Forum Software package.
5 *
6 * @copyright (c) phpBB Limited <https://www.phpbb.com>
7 * @license GNU General Public License, version 2 (GPL-2.0)
8 *
9 * For full copyright and license information, please see
10 * the docs/CREDITS.txt file.
11 *
12 */
13
14namespace phpbb\composer;
15
16use Composer\IO\IOInterface;
17use phpbb\cache\driver\driver_interface;
18use phpbb\composer\exception\managed_with_clean_error_exception;
19use phpbb\composer\exception\managed_with_enable_error_exception;
20use phpbb\composer\exception\runtime_exception;
21use phpbb\config\config;
22use phpbb\extension\manager as ext_manager;
23use phpbb\filesystem\exception\filesystem_exception;
24use phpbb\filesystem\filesystem;
25
26/**
27 * Class to safely manage extensions through composer.
28 */
29class extension_manager extends manager
30{
31    /**
32     * @var ext_manager
33     */
34    protected $extension_manager;
35
36    /**
37     * @var filesystem
38     */
39    protected $filesystem;
40
41    /**
42     * @var string
43     */
44    protected $root_path;
45
46    /**
47     * @var array
48     */
49    private $enabled_extensions;
50
51    /**
52     * @var bool Enables extensions when installing them?
53     */
54    private $enable_on_install = false;
55
56    /**
57     * @var bool Purges extensions data when removing them?
58     */
59    private $purge_on_remove = true;
60
61    /**
62     * @param installer            $installer            Installer object
63     * @param driver_interface    $cache                Cache object
64     * @param ext_manager        $extension_manager    phpBB extension manager
65     * @param filesystem        $filesystem            Filesystem object
66     * @param string            $package_type        Composer type of managed packages
67     * @param string            $exception_prefix    Exception prefix to use
68     * @param string            $root_path            phpBB root path
69     * @param config|null        $config                Config object
70     */
71    public function __construct(installer $installer, driver_interface $cache, ext_manager $extension_manager, filesystem $filesystem, $package_type, $exception_prefix, $root_path, config $config = null)
72    {
73        $this->extension_manager = $extension_manager;
74        $this->filesystem = $filesystem;
75        $this->root_path = $root_path;
76
77        if ($config)
78        {
79            $this->enable_on_install = (bool) $config['exts_composer_enable_on_install'];
80            $this->purge_on_remove   = (bool) $config['exts_composer_purge_on_remove'];
81        }
82
83        parent::__construct($installer, $cache, $package_type, $exception_prefix);
84    }
85
86    /**
87     * {@inheritdoc}
88     */
89    public function pre_install(array $packages, IOInterface $io = null)
90    {
91        $installed_manually = array_intersect(array_keys($this->extension_manager->all_available()), array_keys($packages));
92        if (count($installed_manually) !== 0)
93        {
94            throw new runtime_exception($this->exception_prefix, 'ALREADY_INSTALLED_MANUALLY', [implode('|', array_keys($installed_manually))]);
95        }
96    }
97
98    /**
99     * {@inheritdoc}
100     */
101    public function post_install(array $packages, IOInterface $io = null)
102    {
103        if ($this->enable_on_install)
104        {
105            /** @psalm-suppress InvalidArgument */
106            $io->writeError([['ENABLING_EXTENSIONS', [], 1]]);
107            foreach ($packages as $package => $version)
108            {
109                try
110                {
111                    $this->extension_manager->enable($package);
112                }
113                catch (\phpbb\exception\runtime_exception $e)
114                {
115                    /** @psalm-suppress InvalidArgument */
116                    $io->writeError([[$e->getMessage(), $e->get_parameters(), 4]]);
117                }
118                catch (\Exception $e)
119                {
120                    /** @psalm-suppress InvalidArgument */
121                    $io->writeError([[$e->getMessage(), [], 4]]);
122                }
123            }
124        }
125    }
126
127    /**
128     * {@inheritdoc}
129     */
130    protected function pre_update(array $packages, IOInterface $io = null)
131    {
132        /** @psalm-suppress InvalidArgument */
133        $io->writeError([['DISABLING_EXTENSIONS', [], 1]]);
134        $this->enabled_extensions = [];
135        foreach ($packages as $package => $version)
136        {
137            try
138            {
139                if ($this->extension_manager->is_enabled($package))
140                {
141                    $this->enabled_extensions[] = $package;
142                    $this->extension_manager->disable($package);
143                }
144            }
145            catch (\phpbb\exception\runtime_exception $e)
146            {
147                /** @psalm-suppress InvalidArgument */
148                $io->writeError([[$e->getMessage(), $e->get_parameters(), 4]]);
149            }
150            catch (\Exception $e)
151            {
152                /** @psalm-suppress InvalidArgument */
153                $io->writeError([[$e->getMessage(), [], 4]]);
154            }
155        }
156    }
157
158    /**
159     * {@inheritdoc}
160     */
161    protected function post_update(array $packages, IOInterface $io = null)
162    {
163        /** @psalm-suppress InvalidArgument */
164        $io->writeError([['ENABLING_EXTENSIONS', [], 1]]);
165        foreach ($this->enabled_extensions as $package)
166        {
167            try
168            {
169                $this->extension_manager->enable($package);
170            }
171            catch (\phpbb\exception\runtime_exception $e)
172            {
173                /** @psalm-suppress InvalidArgument */
174                $io->writeError([[$e->getMessage(), $e->get_parameters(), 4]]);
175            }
176            catch (\Exception $e)
177            {
178                /** @psalm-suppress InvalidArgument */
179                $io->writeError([[$e->getMessage(), [], 4]]);
180            }
181        }
182    }
183
184    /**
185     * {@inheritdoc}
186     */
187    public function remove(array $packages, IOInterface $io = null)
188    {
189        $packages = $this->normalize_version($packages);
190
191        $not_installed = array_diff(array_keys($packages), array_keys($this->extension_manager->all_available()));
192        if (count($not_installed) !== 0)
193        {
194            throw new runtime_exception($this->exception_prefix, 'NOT_INSTALLED', [implode('|', array_keys($not_installed))]);
195        }
196
197        parent::remove($packages, $io);
198    }
199
200    /**
201     * {@inheritdoc}
202     */
203    public function pre_remove(array $packages, IOInterface $io = null)
204    {
205        if ($this->purge_on_remove)
206        {
207            /** @psalm-suppress InvalidArgument */
208            $io->writeError([['DISABLING_EXTENSIONS', [], 1]]);
209        }
210
211        foreach ($packages as $package => $version)
212        {
213            try
214            {
215                if ($this->extension_manager->is_enabled($package))
216                {
217                    if ($this->purge_on_remove)
218                    {
219                        $this->extension_manager->purge($package);
220                    }
221                    else
222                    {
223                        $this->extension_manager->disable($package);
224                    }
225                }
226            }
227            catch (\phpbb\exception\runtime_exception $e)
228            {
229                /** @psalm-suppress InvalidArgument */
230                $io->writeError([[$e->getMessage(), $e->get_parameters(), 4]]);
231            }
232            catch (\Exception $e)
233            {
234                /** @psalm-suppress InvalidArgument */
235                $io->writeError([[$e->getMessage(), [], 4]]);
236            }
237        }
238    }
239
240    /**
241     * {@inheritdoc}
242     */
243    public function start_managing($package, $io)
244    {
245        if (!$this->extension_manager->is_available($package))
246        {
247            throw new runtime_exception($this->exception_prefix, 'NOT_INSTALLED', [$package]);
248        }
249
250        if ($this->is_managed($package))
251        {
252            throw new runtime_exception($this->exception_prefix, 'ALREADY_MANAGED', [$package]);
253        }
254
255        $enabled = false;
256        if ($this->extension_manager->is_enabled($package))
257        {
258            $enabled = true;
259            /** @psalm-suppress InvalidArgument */
260            $io->writeError([['DISABLING_EXTENSIONS', [], 1]]);
261            $this->extension_manager->disable($package);
262        }
263
264        $ext_path = $this->extension_manager->get_extension_path($package, true);
265        $backup_path = rtrim($ext_path, '/') . '__backup__';
266
267        try
268        {
269            $this->filesystem->rename($ext_path, $backup_path);
270        }
271        catch (filesystem_exception $e)
272        {
273            throw new runtime_exception($this->exception_prefix, 'CANNOT_MANAGE_FILESYSTEM_ERROR', [$package], $e);
274        }
275
276        try
277        {
278            $this->install((array) $package, $io);
279            $this->filesystem->remove($backup_path);
280        }
281        catch (runtime_exception $e)
282        {
283            $this->filesystem->rename($backup_path, $ext_path);
284            throw new runtime_exception($this->exception_prefix, 'CANNOT_MANAGE_INSTALL_ERROR', [$package], $e);
285        }
286        catch (filesystem_exception $e)
287        {
288            throw new managed_with_clean_error_exception($this->exception_prefix, 'MANAGED_WITH_CLEAN_ERROR', [$package, $backup_path], $e);
289        }
290
291        if ($enabled)
292        {
293            try
294            {
295                /** @psalm-suppress InvalidArgument */
296                $io->writeError([['ENABLING_EXTENSIONS', [], 1]]);
297                $this->extension_manager->enable($package);
298            }
299            catch (\Exception $e)
300            {
301                throw new managed_with_enable_error_exception($this->exception_prefix, 'MANAGED_WITH_ENABLE_ERROR', [$package], $e);
302            }
303        }
304    }
305
306    /**
307     * {@inheritdoc}
308     */
309    public function check_requirements()
310    {
311        return parent::check_requirements() && $this->filesystem->is_writable($this->root_path . 'ext/');
312    }
313
314    /**
315     * Enable the extensions when installing
316     *
317     * Warning: Only the explicitly required extensions will be enabled
318     *
319     * @param bool $enable
320     */
321    public function set_enable_on_install($enable)
322    {
323        $this->enable_on_install = $enable;
324    }
325
326    /**
327     * Purge the extension when disabling it
328     *
329     * @param bool $purge
330     */
331    public function set_purge_on_remove($purge)
332    {
333        $this->purge_on_remove = $purge;
334    }
335}