LocalUpgradePackages   A
last analyzed

Complexity

Total Complexity 18

Size/Duplication

Total Lines 176
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 176
rs 10
wmc 18
lcom 1
cbo 7

6 Methods

Rating   Name   Duplication   Size   Complexity  
A getPotentiallyBrokenCustomizations() 0 6 1
A getUpgradeSteps() 0 8 2
B getChangedFiles() 0 55 7
A getSuitablePackages() 0 21 4
A getFlavPackages() 0 18 3
A getCheckSum() 0 16 1
1
<?php
2
3
namespace Sugarcrm\UpgradeSpec\Data\Provider\SourceCode;
4
5
use Sugarcrm\UpgradeSpec\Data\Exception\WrongProviderException;
6
use Sugarcrm\UpgradeSpec\Context\Upgrade;
7
use Sugarcrm\UpgradeSpec\Version\Graph\AdjacencyList;
8
use Sugarcrm\UpgradeSpec\Version\Graph\Dijkstra;
9
use Sugarcrm\UpgradeSpec\Version\OrderedList;
10
use Sugarcrm\UpgradeSpec\Version\Version;
11
use Symfony\Component\Finder\Finder;
12
13
class LocalUpgradePackages implements SourceCodeProviderInterface
14
{
15
    /**
16
     * @var null|array
17
     */
18
    private $suitablePackages = null;
19
20
    /**
21
     * Gets the list of potentially broken customizations (changed and deleted files)
22
     *
23
     * @param Upgrade $context
24
     *
25
     * @return mixed
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,array>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
26
     */
27
    public function getPotentiallyBrokenCustomizations(Upgrade $context)
28
    {
29
        $packages = $this->getSuitablePackages($context);
30
31
        return $this->getChangedFiles($context->getBuildPath(), $packages);
32
    }
33
34
    /**
35
     * Gets the lists of upgrade steps for the given source
36
     *
37
     * @param Upgrade $context
38
     *
39
     * @return mixed
40
     * @throws WrongProviderException
41
     */
42
    public function getUpgradeSteps(Upgrade $context)
43
    {
44
        if (file_exists($context->getTargetPath() . '/.git')) {
45
            throw new WrongProviderException('This provider uses zipped upgrade packages as source');
46
        }
47
48
        return $this->getSuitablePackages($context);
49
    }
50
51
    /**
52
     * @param $buildPath
53
     * @param $packages
54
     *
55
     * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,array>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
56
     */
57
    private function getChangedFiles($buildPath, $packages)
58
    {
59
        $modifiedFiles = $deletedFiles = $packageZips = [];
60
61
        foreach ($packages as $package) {
62
            $zip = new \ZipArchive();
63
            if (!$zip->open($package)) {
64
                throw new \RuntimeException(sprintf('Can\'t open zip archive: %s', $package));
65
            }
66
67
            $packageZips[$package] = $zip;
68
69
            eval(str_replace(['<?php', '<?', '?>'], '', $zip->getFromName(basename($package, '.zip') . DS . 'files.md5')));
0 ignored issues
show
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
70
            $packageModifiedFiles = array_keys($md5_string);
0 ignored issues
show
Bug introduced by
The variable $md5_string does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
71
72
            if ($filesToRemove = $zip->getFromName('filesToRemove.txt')) {
73
                $packageDeletedFiles = explode(PHP_EOL, str_replace(["\r\n", "\r", "\n"], PHP_EOL, $filesToRemove));
74
            } else if ($filesToRemove = $zip->getFromName('filesToRemove.json')) {
75
                $packageDeletedFiles = json_decode($filesToRemove);
76
            } else {
77
                throw new \RuntimeException('Can\'t open filesToRemove');
78
            }
79
80
            $modifiedFiles = array_merge($modifiedFiles, array_combine($packageModifiedFiles, array_fill(0, count($packageModifiedFiles), $package)));
81
            $deletedFiles = array_diff(array_merge($deletedFiles, $packageDeletedFiles), $packageModifiedFiles);
82
        }
83
84
        $modifiedFiles = array_keys(array_filter($modifiedFiles, function ($package, $changedFile) use ($buildPath, $packageZips) {
85
            if (($buildFile = @file_get_contents($buildPath . DS . $changedFile)) === false) {
86
                return false;
87
            }
88
            $packageFile = $packageZips[$package]->getFromName(basename($package, '.zip') . DS . $changedFile);
89
90
            return $this->getCheckSum($buildFile) != $this->getCheckSum($packageFile);
91
        }, ARRAY_FILTER_USE_BOTH));
92
93
        $deletedFiles = array_values(array_filter($deletedFiles));
94
95
        foreach ($packageZips as $zip) {
96
            $zip->close();
97
        }
98
99
        $modifiedFiles = array_values(array_filter($modifiedFiles, function ($file) use ($buildPath) {
100
            return file_exists($buildPath . '/custom/' . $file);
101
        }));
102
103
        $deletedFiles = array_values(array_filter($deletedFiles, function ($file) use ($buildPath) {
104
            return file_exists($buildPath . '/custom/' . $file);
105
        }));
106
107
        natsort($modifiedFiles);
108
        natsort($deletedFiles);
109
110
        return ['modified_files' => $modifiedFiles, 'deleted_files' => $deletedFiles];
111
    }
112
113
    /**
114
     * @param Upgrade $context
115
     *
116
     * @return array
117
     */
118
    private function getSuitablePackages(Upgrade $context)
119
    {
120
        if (!is_null($this->suitablePackages)) {
121
            return $this->suitablePackages;
122
        }
123
124
        $packages = $this->getFlavPackages($context->getBuildFlav(), $context->getTargetPath());
125
        $graph = new Dijkstra(new AdjacencyList($packages));
126
127
        $this->suitablePackages = array_map(function (array $hop) use ($packages) {
128
            foreach ($packages as $path => $pair) {
129
                if ((new OrderedList($hop))->isEqualTo(new OrderedList($pair))) {
130
                    return $path;
131
                }
132
            }
133
134
            throw new \RuntimeException(sprintf('Package not found: "%s"', $hop));
135
        }, $graph->getPath($context->getBuildVersion(), $context->getTargetVersion()));
0 ignored issues
show
Documentation introduced by
$context->getBuildVersion() is of type string, but the function expects a object<Sugarcrm\UpgradeSpec\Version\Version>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
$context->getTargetVersion() is of type string, but the function expects a object<Sugarcrm\UpgradeSpec\Version\Version>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
136
137
        return $this->suitablePackages;
138
    }
139
140
    /**
141
     * Gets flav specific packages
142
     *
143
     * @param $flav
144
     * @param $packagesPath
145
     *
146
     * @return array
147
     */
148
    private function getFlavPackages($flav, $packagesPath)
149
    {
150
        $versionPattern = '/\d+\.\d+(\.\d+|\.x){1,2}/';
151
        $flav = ucfirst(mb_strtolower($flav));
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $flav. This often makes code more readable.
Loading history...
152
        $packagePattern = sprintf('/^Sugar%1$s-Upgrade-%2$s-to-%2$s.zip$/', $flav, trim($versionPattern, '/'));
153
154
        $packages = [];
155
        foreach ((new Finder())->files()->in($packagesPath)->name($packagePattern) as $package) {
156
            if (preg_match_all($versionPattern, $package, $matches)) {
157
                $packages[$package->getRealPath()] = [
158
                    new Version(str_replace('.x', '', $matches[0][0])),
159
                    new Version(str_replace('.x', '', $matches[0][1]))
160
                ];
161
            }
162
        }
163
164
        return $packages;
165
    }
166
167
    /**
168
     * @param $content
169
     *
170
     * @return string
171
     */
172
    private function getCheckSum($content)
173
    {
174
        // remove license comments
175
        $content = preg_replace('/\/\*.*?Copyright \(C\) SugarCRM.*?\*\//is', '', $content);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $content. This often makes code more readable.
Loading history...
176
177
        // remove blank lines
178
        $content = preg_replace('/(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+/', "\n", $content);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $content. This often makes code more readable.
Loading history...
179
180
        // change all line breaks to system line break
181
        $content = str_replace(["\r\n", "\r", "\n"], PHP_EOL, $content);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $content. This often makes code more readable.
Loading history...
182
183
        // remove trailing whitespaces
184
        $content = preg_replace('/^\s+|\s+$/m', '', $content);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $content. This often makes code more readable.
Loading history...
185
186
        return md5($content);
187
    }
188
}
189