Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 234
0.00% covered (danger)
0.00%
0 / 25
CRAP
0.00% covered (danger)
0.00%
0 / 1
installer
0.00% covered (danger)
0.00%
0 / 234
0.00% covered (danger)
0.00%
0 / 25
4830
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
20
 install
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 do_install
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
20
 get_installed_packages
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 get_composer
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 do_get_installed_packages
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
30
 get_available_packages
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 do_get_available_packages
0.00% covered (danger)
0.00%
0 / 58
0.00% covered (danger)
0.00%
0 / 1
210
 check_requirements
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 get_compatible_versions
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
110
 generate_ext_json_file
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
6
 restore_ext_json_file
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 get_core_packages
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 get_core_php_requirement
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_composer_repositories
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
30
 get_composer_ext_json_filename
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_extra_dependencies
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 set_repositories
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 set_packagist
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 set_composer_filename
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 set_packages_vendor_dir
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 set_root_path
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 move_to_root
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 restore_cwd
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 wrap
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
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\Composer;
17use Composer\DependencyResolver\Request as composer_request;
18use Composer\Factory;
19use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
20use Composer\IO\IOInterface;
21use Composer\IO\NullIO;
22use Composer\Json\JsonFile;
23use Composer\Json\JsonValidationException;
24use Composer\Package\BasePackage;
25use Composer\Package\CompleteAliasPackage;
26use Composer\Package\CompletePackage;
27use Composer\Package\PackageInterface;
28use Composer\PartialComposer;
29use Composer\Repository\ComposerRepository;
30use Composer\Semver\Constraint\ConstraintInterface;
31use Composer\Semver\VersionParser;
32use Composer\Util\HttpDownloader;
33use phpbb\composer\io\null_io;
34use phpbb\config\config;
35use phpbb\exception\runtime_exception;
36use phpbb\filesystem\filesystem;
37use phpbb\request\request;
38use Seld\JsonLint\ParsingException;
39use phpbb\filesystem\helper as filesystem_helper;
40
41/**
42 * Class to install packages through composer while freezing core dependencies.
43 */
44class installer
45{
46    const PHPBB_TYPES = 'phpbb-extension,phpbb-style,phpbb-language';
47
48    /**
49     * @var array Repositories to look packages from
50     */
51    protected $repositories = [];
52
53    /**
54     * @var bool Indicates whether packagist usage is allowed or not
55     */
56    protected $packagist = false;
57
58    /**
59     * @var string Composer filename used to manage the packages
60     */
61    protected $composer_filename = 'composer-ext.json';
62
63    /**
64     * @var string Directory where to install packages vendors
65     */
66    protected $packages_vendor_dir = 'vendor-ext/';
67
68    /**
69     * @var string Minimum stability
70     */
71    protected $minimum_stability = 'stable';
72
73    /**
74     * @var string phpBB root path
75     */
76    protected $root_path;
77
78    /**
79     * @var string|null Stores the original working directory in case it has been changed through move_to_root()
80     */
81    private $original_cwd;
82
83    /**
84     * @var array|null Stores the content of the ext json file before generate_ext_json_file() overrides it
85     */
86    private $ext_json_file_backup;
87
88    /**
89     * @var request phpBB request object
90     */
91    private $request;
92
93    /**
94     * @var filesystem phpBB filesystem
95     */
96    private $filesystem;
97
98    /**
99     * @param string        $root_path    phpBB root path
100     * @param filesystem    $filesystem    Filesystem object
101     * @param request        $request    phpBB request object
102     * @param config|null        $config        Config object
103     */
104    public function __construct($root_path, filesystem $filesystem, request $request, config $config = null)
105    {
106        if ($config)
107        {
108            $repositories = json_decode($config['exts_composer_repositories'], true);
109
110            if (is_array($repositories) && !empty($repositories))
111            {
112                $this->repositories = (array) $repositories;
113            }
114
115            $this->packagist            = (bool) $config['exts_composer_packagist'];
116            $this->composer_filename    = $config['exts_composer_json_file'];
117            $this->packages_vendor_dir    = $config['exts_composer_vendor_dir'];
118            $this->minimum_stability    = $config['exts_composer_minimum_stability'];
119        }
120
121        $this->root_path = $root_path;
122        $this->request = $request;
123        $this->filesystem = $filesystem;
124
125        putenv('COMPOSER_HOME=' . filesystem_helper::realpath($root_path) . '/store/composer');
126    }
127
128    /**
129     * Update the current installed set of packages
130     *
131     * @param array $packages Packages to install.
132     *        Each entry may be a name or an array associating a version constraint to a name
133     * @param array $whitelist White-listed packages (packages that can be installed/updated/removed)
134     * @param IOInterface|null $io IO object used for the output
135     *
136     * @throws runtime_exception
137     */
138    public function install(array $packages, $whitelist, IOInterface $io = null)
139    {
140        $this->wrap(function() use ($packages, $whitelist, $io) {
141            $this->do_install($packages, $whitelist, $io);
142        });
143    }
144
145    /**
146     * Update the current installed set of packages
147     *
148     * /!\ Doesn't change the current working directory
149     *
150     * @param array $packages Packages to install.
151     *        Each entry may be a name or an array associating a version constraint to a name
152     * @param array $whitelist White-listed packages (packages that can be installed/updated/removed)
153     * @param io\io_interface|null $io IO object used for the output
154     *
155     * @throws runtime_exception
156     * @throws JsonValidationException
157     */
158    protected function do_install(array $packages, $whitelist, io\io_interface $io = null)
159    {
160        if (!$io)
161        {
162            $this->restore_cwd();
163            $io = new null_io();
164            $this->move_to_root();
165        }
166
167        $this->generate_ext_json_file($packages);
168
169        $composer = $this->get_composer($this->get_composer_ext_json_filename());
170
171        $install = \Composer\Installer::create($io, $composer);
172
173        $composer->getInstallationManager()->setOutputProgress(false);
174
175        $install
176            ->setVerbose(true)
177            ->setPreferSource(false)
178            ->setPreferDist(true)
179            ->setDevMode(false)
180            ->setUpdate(true)
181            ->setUpdateAllowList($whitelist)
182            ->setUpdateAllowTransitiveDependencies(composer_request::UPDATE_ONLY_LISTED)
183            ->setPlatformRequirementFilter(PlatformRequirementFilterFactory::fromBoolOrList(false))
184            ->setOptimizeAutoloader(true)
185            ->setDumpAutoloader(true)
186            ->setPreferStable(true)
187            ->setRunScripts(false)
188            ->setDryRun(false);
189
190        try
191        {
192            $result = $install->run();
193        }
194        catch (\Exception $e)
195        {
196            $this->restore_ext_json_file();
197            $this->restore_cwd();
198
199            throw new runtime_exception('COMPOSER_CANNOT_INSTALL', [], $e);
200        }
201
202        if ($result !== 0)
203        {
204            $this->restore_ext_json_file();
205            $this->restore_cwd();
206
207            throw new runtime_exception($io->get_composer_error(), []);
208        }
209    }
210
211    /**
212     * Returns the list of currently installed packages
213     *
214     * @param string|array $types Returns only the packages with the given type(s)
215     *
216     * @return array The installed packages associated to their version.
217     *
218     * @throws runtime_exception
219     */
220    public function get_installed_packages($types)
221    {
222        return $this->wrap(function() use ($types) {
223            return $this->do_get_installed_packages($types);
224        });
225    }
226
227    /**
228     * Create instance of composer for supplied config file
229     *
230     * @param string|null $config_file Path to config file relative to phpBB root dir or null
231     *
232     * @return Composer|PartialComposer
233     * @throws JsonValidationException
234     */
235    protected function get_composer(?string $config_file): PartialComposer
236    {
237        static $composer_factory;
238        if (!$composer_factory)
239        {
240            $composer_factory = new Factory();
241        }
242
243        $io = new NullIO();
244
245        return $composer_factory->createComposer(
246            $io,
247            $config_file,
248            false,
249            filesystem_helper::realpath('')
250        );
251    }
252
253    /**
254     * Returns the list of currently installed packages
255     *
256     * /!\ Doesn't change the current working directory
257     *
258     * @param string|array $types Returns only the packages with the given type(s)
259     *
260     * @return array The installed packages associated to their version.
261     */
262    protected function do_get_installed_packages($types)
263    {
264        $types = (array) $types;
265
266        try
267        {
268            $composer = $this->get_composer($this->get_composer_ext_json_filename());
269
270            $installed = [];
271
272            /** @var \Composer\Package\Link[] $required_links */
273            $required_links = $composer->getPackage()->getRequires();
274            $installed_packages = $composer->getRepositoryManager()->getLocalRepository()->getCanonicalPackages();
275
276            foreach ($installed_packages as $package)
277            {
278                if (in_array($package->getType(), $types, true))
279                {
280                    $version = array_key_exists($package->getName(), $required_links) ?
281                        $required_links[$package->getName()]->getPrettyConstraint() : '*';
282                    $installed[$package->getName()] = $version;
283                }
284            }
285
286            return $installed;
287        }
288        catch (\Exception $e)
289        {
290            return [];
291        }
292    }
293
294    /**
295     * Gets the list of the available packages of the configured type in the configured repositories
296     *
297     * /!\ Doesn't change the current working directory
298     *
299     * @param string $type Returns only the packages with the given type
300     *
301     * @return array The name of the available packages, associated to their definition. Ordered by name.
302     *
303     * @throws runtime_exception
304     */
305    public function get_available_packages($type)
306    {
307        return $this->wrap(function() use ($type) {
308            return $this->do_get_available_packages($type);
309        });
310    }
311
312    /**
313     * Gets the list of the available packages of the configured type in the configured repositories
314     *
315     * @param string $type Returns only the packages with the given type
316     *
317     * @return array The name of the available packages, associated to their definition. Ordered by name.
318     */
319    protected function do_get_available_packages($type)
320    {
321        try
322        {
323            $this->generate_ext_json_file($this->do_get_installed_packages(explode(',', self::PHPBB_TYPES)));
324
325            $io = new NullIO();
326            $composer = $this->get_composer($this->get_composer_ext_json_filename());
327
328            /** @var ConstraintInterface $core_constraint */
329            $core_constraint = $composer->getPackage()->getRequires()['phpbb/phpbb']->getConstraint();
330            $core_stability = $composer->getPackage()->getMinimumStability();
331
332            $available = [];
333
334            $compatible_packages = [];
335            $repositories = $composer->getRepositoryManager()->getRepositories();
336
337            /** @var \Composer\Repository\RepositoryInterface $repository */
338            foreach ($repositories as $repository)
339            {
340                try
341                {
342                    if ($repository instanceof ComposerRepository)
343                    {
344                        // Special case for packagist which exposes an api to retrieve all packages of a given type.
345                        // For the others composer repositories with providers we can't do anything. It would be too slow.
346
347                        $repositoryReflection = new \ReflectionObject($repository);
348                        $repo_url = $repositoryReflection->getProperty('url');
349                        $repo_url->setAccessible(true);
350
351                        if ($repo_url->getValue($repository) === 'https://repo.packagist.org')
352                        {
353                            $url = 'https://packagist.org/packages/list.json?type=' . $type;
354                            $composer_config = new \Composer\Config();
355                            $downloader = new HttpDownloader($io, $composer_config);
356                            $json = $downloader->get($url)->getBody();
357
358                            /** @var PackageInterface $package */
359                            foreach (JsonFile::parseJson($json, $url)['packageNames'] as $package)
360                            {
361                                $versions            = $repository->findPackages($package);
362                                $compatible_packages = $this->get_compatible_versions($compatible_packages, $core_constraint, $core_stability, $package, $versions);
363                            }
364                        }
365                    }
366                    else
367                    {
368                        // Pre-filter repo packages by their type
369                        $packages = [];
370                        /** @var PackageInterface $package */
371                        foreach ($repository->getPackages() as $package)
372                        {
373                            if ($package->getType() === $type)
374                            {
375                                $packages[$package->getName()][] = $package;
376                            }
377                        }
378
379                        // Filter the compatibles versions
380                        foreach ($packages as $package => $versions)
381                        {
382                            $compatible_packages = $this->get_compatible_versions($compatible_packages, $core_constraint, $core_stability, $package, $versions);
383                        }
384                    }
385                }
386                catch (\Exception $e)
387                {
388                    // If a repo fails, just skip it.
389                    continue;
390                }
391            }
392
393            foreach ($compatible_packages as $name => $versions)
394            {
395                // Determine the highest version of the package
396                /** @var CompletePackage|CompleteAliasPackage $highest_version */
397                $highest_version = null;
398
399                // Sort the versions array in descending order
400                usort($versions, function ($a, $b)
401                {
402                    return version_compare($b->getVersion(), $a->getVersion());
403                });
404
405                // The first element in the sorted array is the highest version
406                if (!empty($versions))
407                {
408                    $highest_version = $versions[0];
409
410                    // If highest version is a non-numeric dev branch, it's an instance of CompleteAliasPackage,
411                    // so we need to get the package being aliased in order to show the true non-numeric version.
412                    if ($highest_version instanceof CompleteAliasPackage)
413                    {
414                        $highest_version = $highest_version->getAliasOf();
415                    }
416                }
417
418                // Generates the entry
419                $available[$name] = [];
420                $available[$name]['name'] = $highest_version->getPrettyName();
421                $available[$name]['display_name'] = $highest_version->getExtra()['display-name'];
422                $available[$name]['composer_name'] = $highest_version->getName();
423                $available[$name]['version'] = $highest_version->getPrettyVersion();
424
425                if ($highest_version instanceof CompletePackage)
426                {
427                    $available[$name]['description'] = $highest_version->getDescription();
428                    $available[$name]['url'] = $highest_version->getHomepage();
429                    $available[$name]['authors'] = $highest_version->getAuthors();
430                }
431                else
432                {
433                    $available[$name]['description'] = '';
434                    $available[$name]['url'] = '';
435                    $available[$name]['authors'] = [];
436                }
437            }
438
439            usort($available, function($a, $b)
440            {
441                return strcasecmp($a['display_name'], $b['display_name']);
442            });
443
444            return $available;
445        }
446        catch (\Exception $e)
447        {
448            return [];
449        }
450    }
451
452    /**
453     * Checks the requirements of the manager and returns true if it can be used.
454     *
455     * @return bool
456     */
457    public function check_requirements()
458    {
459        return $this->filesystem->is_writable([
460            $this->root_path . $this->composer_filename,
461            $this->root_path . $this->packages_vendor_dir,
462            $this->root_path . substr($this->composer_filename, 0, -5) . '.lock',
463        ]);
464    }
465
466    /**
467     * Updates $compatible_packages with the versions of $versions compatibles with the $core_constraint
468     *
469     * @param array $compatible_packages List of compatibles versions
470     * @param ConstraintInterface $core_constraint Constraint against the phpBB version
471     * @param string $core_stability Core stability
472     * @param string $package_name Considered package
473     * @param array $versions List of available versions
474     *
475     * @return array
476     */
477    private function get_compatible_versions(array $compatible_packages, ConstraintInterface $core_constraint, $core_stability, $package_name, array $versions)
478    {
479        $version_parser = new VersionParser();
480
481        $core_stability_value = BasePackage::$stabilities[$core_stability];
482
483        /** @var PackageInterface $version */
484        foreach ($versions as $version)
485        {
486            try
487            {
488                // Check stability first to avoid unnecessary operations
489                if (BasePackage::$stabilities[$version->getStability()] > $core_stability_value)
490                {
491                    continue;
492                }
493
494                $requires = $version->getRequires();
495                $extra = $version->getExtra();
496
497                // Check for compatibility with phpBB if 'phpbb/phpbb' exists in 'requires'
498                if (isset($requires['phpbb/phpbb']))
499                {
500                    $package_constraint = $requires['phpbb/phpbb']->getConstraint();
501                    if (!$package_constraint->matches($core_constraint))
502                    {
503                        continue;
504                    }
505                }
506
507                // Check for compatibility with phpBB if 'phpbb/phpbb' exists in 'soft-require'
508                if (isset($extra['soft-require']['phpbb/phpbb']))
509                {
510                    $package_constraint = $version_parser->parseConstraints($extra['soft-require']['phpbb/phpbb']);
511                    if (!$package_constraint->matches($core_constraint))
512                    {
513                        continue;
514                    }
515                }
516
517                // Check for compatibility with php if 'php' exists in 'requires'
518                if (isset($requires['php']))
519                {
520                    $php_constraint = $version_parser->parseConstraints(PHP_VERSION);
521                    $package_constraint = $requires['php']->getConstraint();
522                    if (!$package_constraint->matches($php_constraint))
523                    {
524                        continue;
525                    }
526                }
527
528                $compatible_packages[$package_name][] = $version;
529            }
530            catch (\Exception $e)
531            {
532                // Do nothing (to log when a true debug logger is available)
533            }
534        }
535
536        return $compatible_packages;
537    }
538
539    /**
540     * Generates and write the json file used to install the set of packages
541     *
542     * @param array $packages Packages to update.
543     *        Each entry may be a name or an array associating a version constraint to a name
544     * @throws JsonValidationException
545     */
546    protected function generate_ext_json_file(array $packages)
547    {
548        $io = new NullIO();
549
550        $composer = $this->get_composer(null);
551
552        $core_packages = $this->get_core_packages($composer);
553
554        // The composer/installers package must be installed on his own and not provided by the existing autoloader
555        $core_replace = $core_packages;
556        unset($core_replace['composer/installers']);
557
558        $ext_json_data = [
559            'require' => array_merge(
560                ['php' => $this->get_core_php_requirement($composer)],
561                $core_packages,
562                $this->get_extra_dependencies(),
563                $packages),
564            'replace' => $core_replace,
565            'repositories' => $this->get_composer_repositories(),
566            'config' => [
567                'vendor-dir'    => $this->packages_vendor_dir,
568                'allow-plugins'    => [
569                    'composer/installers' => true,
570                ]
571            ],
572            'minimum-stability' => $this->minimum_stability,
573        ];
574
575        $this->ext_json_file_backup = null;
576        $json_file = new JsonFile($this->get_composer_ext_json_filename());
577
578        try
579        {
580            $ext_json_file_backup = $json_file->read();
581        }
582        catch (ParsingException $e)
583        {
584            $ext_json_file_backup = '{}';
585
586            $lockFile = new JsonFile(substr($this->get_composer_ext_json_filename(), 0, -5) . '.lock');
587            $lockFile->write([]);
588        }
589
590        $json_file->write($ext_json_data);
591        $this->ext_json_file_backup = $ext_json_file_backup;
592    }
593
594    /**
595     * Restore the json file overridden by generate_ext_json_file()
596     */
597    protected function restore_ext_json_file()
598    {
599        if ($this->ext_json_file_backup)
600        {
601            try
602            {
603                $json_file = new JsonFile($this->get_composer_ext_json_filename());
604                $json_file->write($this->ext_json_file_backup);
605            }
606            catch (\Exception $e)
607            {
608            }
609
610            $this->ext_json_file_backup = null;
611        }
612    }
613
614    /**
615     * Get the core installed packages
616     *
617     * @param Composer $composer Composer object to load the dependencies
618     * @return array The core packages with their version
619     */
620    protected function get_core_packages(Composer $composer)
621    {
622        $core_deps = [];
623        $packages = $composer->getRepositoryManager()->getLocalRepository()->getCanonicalPackages();
624
625        foreach ($packages as $package)
626        {
627            $core_deps[$package->getName()] = $package->getPrettyVersion();
628        }
629
630        $core_deps['phpbb/phpbb'] = PHPBB_VERSION;
631
632        return $core_deps;
633    }
634
635    /**
636     * Get the PHP version required by the core
637     *
638     * @param Composer $composer Composer object to load the dependencies
639     * @return string The PHP version required by the core
640     */
641    protected function get_core_php_requirement(Composer $composer)
642    {
643        return $composer->getLocker()->getLockData()['platform']['php'];
644    }
645
646    /**
647     * Generate the repositories entry of the packages json file
648     *
649     * @return array repositories entry
650     */
651    protected function get_composer_repositories()
652    {
653        $repositories = [];
654
655        if (!$this->packagist)
656        {
657            $repositories[]['packagist'] = false;
658        }
659
660        foreach ($this->repositories as $repository)
661        {
662            if (preg_match('#^' . get_preg_expression('url') . '$#iu', $repository))
663            {
664                $repositories[] = [
665                    'type' => 'composer',
666                    'url' => $repository,
667                    'canonical' => $this->packagist ? false : true,
668                ];
669            }
670        }
671
672        return $repositories;
673    }
674
675    /**
676     * Get the name of the json file used for the packages.
677     *
678     * @return string The json filename
679     */
680    protected function get_composer_ext_json_filename()
681    {
682        return $this->composer_filename;
683    }
684
685    /**
686     * Get extra dependencies required to install the packages
687     *
688     * @return array Array of composer dependencies
689     */
690    protected function get_extra_dependencies()
691    {
692        return [];
693    }
694
695    /**
696     * Sets the customs repositories
697     *
698     * @param array $repositories An array of composer repositories to use
699     */
700    public function set_repositories(array $repositories)
701    {
702        $this->repositories = $repositories;
703    }
704
705    /**
706     * Allow or disallow packagist
707     *
708     * @param boolean $packagist
709     */
710    public function set_packagist($packagist)
711    {
712        $this->packagist = $packagist;
713    }
714
715    /**
716     * Sets the name of the managed packages' json file
717     *
718     * @param string $composer_filename
719     */
720    public function set_composer_filename($composer_filename)
721    {
722        $this->composer_filename = $composer_filename;
723    }
724
725    /**
726     * Sets the location of the managed packages' vendors
727     *
728     * @param string $packages_vendor_dir
729     */
730    public function set_packages_vendor_dir($packages_vendor_dir)
731    {
732        $this->packages_vendor_dir = $packages_vendor_dir;
733    }
734
735    /**
736     * Sets the phpBB root path
737     *
738     * @param string $root_path
739     */
740    public function set_root_path($root_path)
741    {
742        $this->root_path = $root_path;
743    }
744
745    /**
746     * Change the current directory to phpBB root
747     */
748    protected function move_to_root()
749    {
750        if ($this->original_cwd === null)
751        {
752            $this->original_cwd = getcwd();
753            chdir($this->root_path);
754        }
755    }
756
757    /**
758     * Restore the current working directory if move_to_root() have been called
759     */
760    protected function restore_cwd()
761    {
762        if ($this->original_cwd)
763        {
764            chdir($this->original_cwd);
765            $this->original_cwd = null;
766        }
767    }
768
769    /**
770     * Wraps a callable in order to adjust the context needed by composer
771     *
772     * @param callable $callable
773     *
774     * @return mixed
775     */
776    protected function wrap(callable $callable)
777    {
778        // The composer installers works with a path relative to the current directory
779        $this->move_to_root();
780
781        // The composer installers uses some super globals
782        $super_globals = $this->request->super_globals_disabled();
783        $this->request->enable_super_globals();
784
785        try
786        {
787            return $callable();
788        }
789        finally
790        {
791            $this->restore_cwd();
792
793            if ($super_globals)
794            {
795                $this->request->disable_super_globals();
796            }
797        }
798    }
799}