Passed
Pull Request — master (#56)
by Guy
10:03
created

ComposerLoaderExtension::getComposer()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
namespace BringYourOwnIdeas\UpdateChecker\Extensions;
4
5
use BringYourOwnIdeas\Maintenance\Tasks\UpdatePackageInfoTask;
6
use BringYourOwnIdeas\UpdateChecker\DriverReflection;
7
use Composer\Composer;
8
use Composer\Factory;
9
use Composer\IO\NullIO;
10
use Composer\Package\Link;
11
use Composer\Repository\ArrayRepository;
12
use Composer\Repository\BaseRepository;
13
use Composer\Repository\CompositeRepository;
14
use Composer\Repository\RepositoryInterface;
15
use Composer\Repository\RepositoryManager;
16
use Composer\Repository\Vcs\VcsDriverInterface;
17
use Composer\Repository\VcsRepository;
18
use SilverStripe\Core\Environment;
19
use SilverStripe\Core\Extension;
20
21
class ComposerLoaderExtension extends Extension
22
{
23
    /**
24
     * @var Composer
25
     */
26
    protected $composer;
27
28
    /**
29
     * @param Composer $composer
30
     * @return $this
31
     */
32
    public function setComposer(Composer $composer)
33
    {
34
        $this->composer = $composer;
35
        return $this;
36
    }
37
38
    /**
39
     * @return Composer
40
     */
41
    public function getComposer()
42
    {
43
        return $this->composer;
44
    }
45
46
    /**
47
     * Retrieve an array of primary composer dependencies from composer.json.
48
     *
49
     * Packages are filtered by allowed type.
50
     *
51
     * @param array|null $allowedTypes An array of "allowed" package types. Dependencies in composer.json that do not
52
     *                                 match any of the given types are not returned.
53
     * @return array[]
54
     */
55
    public function getPackages(array $allowedTypes = null)
56
    {
57
        $packages = [];
58
        $repository = $this->getRepository();
59
        foreach ($repository->getPackages() as $package) {
60
            // Filter out packages that are not "allowed types"
61
            if (is_array($allowedTypes) && !in_array($package->getType(), $allowedTypes)) {
62
                continue;
63
            }
64
65
            // Find the constraint used for installation
66
            $constraint = $this->getInstalledConstraint($repository, $package->getName());
67
            $packages[$package->getName()] = [
68
                'constraint' => $constraint,
69
                'package' => $package,
70
            ];
71
        }
72
        return $packages;
73
    }
74
75
    /**
76
     * Provides access to the Composer repository
77
     *
78
     * @return RepositoryInterface
79
     */
80
    protected function getRepository()
81
    {
82
        /** @var Composer $composer */
83
        $composer = $this->getComposer();
84
85
        /** @var BaseRepository $repository */
86
        return new CompositeRepository([
87
            new ArrayRepository([$composer->getPackage()]),
88
            $composer->getRepositoryManager()->getLocalRepository(),
89
        ]);
90
    }
91
92
    /**
93
     * Find all dependency constraints for the given package in the current repository and return the strictest one
94
     *
95
     * @param BaseRepository $repository
96
     * @param string $packageName
97
     * @return string
98
     */
99
    protected function getInstalledConstraint(BaseRepository $repository, $packageName)
100
    {
101
        $constraints = [];
102
        foreach ($repository->getDependents($packageName) as $dependent) {
103
            /** @var Link $link */
104
            list (, $link) = $dependent;
105
            $constraints[] = $link->getPrettyConstraint();
106
        }
107
108
        usort($constraints, 'version_compare');
109
110
        return array_pop($constraints);
111
    }
112
113
    /**
114
     * Builds an instance of Composer
115
     */
116
    public function onAfterBuild()
117
    {
118
        // Mock COMPOSER_HOME if it's not defined already. Composer requires one of the two to be set.
119
        if (!Environment::getEnv('COMPOSER_HOME')) {
120
            $home = Environment::getEnv('HOME');
121
            if (!$home || !is_dir($home) || !is_writable($home)) {
122
                // Set our own directory
123
                putenv('COMPOSER_HOME=' . sys_get_temp_dir());
124
            }
125
        }
126
127
        $originalDir = getcwd();
128
        chdir(BASE_PATH);
129
        /** @var Composer $composer */
130
        $composer = Factory::create($io = new NullIO());
131
132
        // Don't include inaccessible repositories.
133
        $inaccessiblePackages = (array)UpdatePackageInfoTask::config()->get('inaccessible_packages');
134
        $inaccessibleHosts = (array)UpdatePackageInfoTask::config()->get('inaccessible_repository_hosts');
135
        if (!empty($doNotFetch) || !empty($doNotFetchHosts)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $doNotFetch seems to never exist and therefore empty should always be true.
Loading history...
Comprehensibility Best Practice introduced by
The variable $doNotFetchHosts seems to never exist and therefore empty should always be true.
Loading history...
136
            $oldManager = $composer->getRepositoryManager();
137
            $manager = new RepositoryManager(
138
                $io,
139
                $composer->getConfig(),
140
                $composer->getEventDispatcher(),
141
                Factory::createRemoteFilesystem($io, $composer->getConfig())
142
            );
143
            $manager->setLocalRepository($oldManager->getLocalRepository());
144
            foreach ($oldManager->getRepositories() as $repo) {
145
                if ($repo instanceof VcsRepository) {
146
                    /** @var VcsDriverInterface $driver */
147
                    $driver = DriverReflection::getDriverWithoutException($repo);
148
                    $sshUrl = DriverReflection::getSshUrl($driver);
149
                    $sourceURL = $driver->getUrl();
150
                    $package = $this->findPackageByUrl($sourceURL);
151
                    if (!$package && $sshUrl) {
152
                        $package = $this->findPackageByUrl($sshUrl);
153
                    }
154
                    // Don't add the repository if we can confirm it's inaccessible.
155
                    // Otherwise the UpdateChecker will attempt to fetch packages using the VcsDriver.
156
                    if (
157
                        ($package && in_array($package->name, $inaccessiblePackages))
158
                        || in_array(parse_url($sourceURL, PHP_URL_HOST), $inaccessibleHosts)
159
                        || ($sshUrl && in_array(preg_replace('/^([^@]+@)?([^:]+):.*/', '$2', $sshUrl), $inaccessibleHosts))
160
                    ) {
161
                        continue;
162
                    }
163
                }
164
                $manager->addRepository($repo);
165
            }
166
            $composer->setRepositoryManager($manager);
167
        }
168
169
        $this->setComposer($composer);
170
        chdir($originalDir);
171
    }
172
173
    public function findPackageByUrl(string $url, bool $includeDev = true)
174
    {
175
        $lock = $this->owner->getLock();
176
        foreach ($lock->packages as $package) {
177
            if (isset($package->source->url) && $package->source->url === $url) {
178
                return $package;
179
            }
180
            if (isset($package->dist->url) && $package->dist->url === $url) {
181
                return $package;
182
            }
183
        }
184
        if ($includeDev) {
185
            foreach ($lock->{'packages-dev'} as $package) {
186
                if (isset($package->source->url) && $package->source->url === $url) {
187
                    return $package;
188
                }
189
                if (isset($package->dist->url) && $package->dist->url === $url) {
190
                    return $package;
191
                }
192
            }
193
        }
194
    }
195
}
196